Full Code of TomWright/dasel for AI

master 3a48935d184b cached
209 files
499.4 KB
160.6k tokens
723 symbols
1 requests
Download .txt
Showing preview only (545K chars total). Download the full file or copy to clipboard to get everything.
Repository: TomWright/dasel
Branch: master
Commit: 3a48935d184b
Files: 209
Total size: 499.4 KB

Directory structure:
gitextract_rq16o89b/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── build-dev.yaml
│       ├── build-test.yaml
│       ├── build.yaml
│       ├── bump-homebrew.yaml
│       ├── codeql-analysis.yml
│       ├── container.yaml
│       ├── golangci-lint.yaml
│       └── test.yaml
├── .gitignore
├── .golangci.yaml
├── .pre-commit-hooks.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── api.go
├── api_example_test.go
├── api_test.go
├── codecov.yaml
├── execution/
│   ├── README.md
│   ├── context.go
│   ├── execute.go
│   ├── execute_array.go
│   ├── execute_array_test.go
│   ├── execute_assign.go
│   ├── execute_assign_test.go
│   ├── execute_binary.go
│   ├── execute_binary_test.go
│   ├── execute_branch.go
│   ├── execute_branch_test.go
│   ├── execute_conditional.go
│   ├── execute_conditional_test.go
│   ├── execute_each.go
│   ├── execute_each_test.go
│   ├── execute_filter.go
│   ├── execute_filter_test.go
│   ├── execute_func.go
│   ├── execute_func_test.go
│   ├── execute_literal.go
│   ├── execute_literal_test.go
│   ├── execute_map.go
│   ├── execute_map_test.go
│   ├── execute_object.go
│   ├── execute_object_test.go
│   ├── execute_recursive_descent.go
│   ├── execute_search.go
│   ├── execute_sort_by.go
│   ├── execute_sort_by_test.go
│   ├── execute_spread.go
│   ├── execute_spread_test.go
│   ├── execute_test.go
│   ├── execute_unary.go
│   ├── execute_unary_test.go
│   ├── func.go
│   ├── func_add.go
│   ├── func_add_test.go
│   ├── func_base64.go
│   ├── func_contains.go
│   ├── func_contains_test.go
│   ├── func_get.go
│   ├── func_get_test.go
│   ├── func_has.go
│   ├── func_has_test.go
│   ├── func_ignore.go
│   ├── func_join.go
│   ├── func_join_test.go
│   ├── func_keys.go
│   ├── func_keys_test.go
│   ├── func_len.go
│   ├── func_len_test.go
│   ├── func_max.go
│   ├── func_max_test.go
│   ├── func_merge.go
│   ├── func_merge_test.go
│   ├── func_min.go
│   ├── func_min_test.go
│   ├── func_parse.go
│   ├── func_parse_test.go
│   ├── func_readfile.go
│   ├── func_replace.go
│   ├── func_replace_test.go
│   ├── func_reverse.go
│   ├── func_reverse_test.go
│   ├── func_sum.go
│   ├── func_sum_test.go
│   ├── func_to_float.go
│   ├── func_to_float_test.go
│   ├── func_to_int.go
│   ├── func_to_int_test.go
│   ├── func_to_string.go
│   ├── func_to_string_test.go
│   ├── func_type_of.go
│   ├── func_type_of_test.go
│   └── options.go
├── go.mod
├── go.sum
├── internal/
│   ├── cli/
│   │   ├── command.go
│   │   ├── command_test.go
│   │   ├── config.go
│   │   ├── generic_test.go
│   │   ├── interactive.go
│   │   ├── interactive_tea.go
│   │   ├── interactive_tea_input.go
│   │   ├── interactive_tea_output.go
│   │   ├── query.go
│   │   ├── read_write_flag.go
│   │   ├── run.go
│   │   ├── variable.go
│   │   └── version.go
│   ├── ptr/
│   │   ├── to.go
│   │   └── to_test.go
│   └── version.go
├── model/
│   ├── README.md
│   ├── error.go
│   ├── go_value.go
│   ├── go_value_test.go
│   ├── orderedmap/
│   │   └── map.go
│   ├── value.go
│   ├── value_comparison.go
│   ├── value_comparison_test.go
│   ├── value_literal.go
│   ├── value_literal_test.go
│   ├── value_map.go
│   ├── value_map_test.go
│   ├── value_math.go
│   ├── value_math_test.go
│   ├── value_metadata.go
│   ├── value_metadata_test.go
│   ├── value_set.go
│   ├── value_set_test.go
│   ├── value_slice.go
│   ├── value_slice_test.go
│   └── value_test.go
├── parsing/
│   ├── csv/
│   │   ├── csv.go
│   │   ├── csv_test.go
│   │   ├── reader.go
│   │   ├── reader_test.go
│   │   ├── writer.go
│   │   └── writer_test.go
│   ├── d/
│   │   └── reader.go
│   ├── format.go
│   ├── hcl/
│   │   ├── hcl.go
│   │   ├── reader.go
│   │   ├── reader_test.go
│   │   ├── writer.go
│   │   └── writer_test.go
│   ├── ini/
│   │   ├── ini.go
│   │   ├── ini_reader.go
│   │   ├── ini_test.go
│   │   └── ini_writer.go
│   ├── json/
│   │   ├── json.go
│   │   ├── json_reader.go
│   │   ├── json_test.go
│   │   └── json_writer.go
│   ├── reader.go
│   ├── toml/
│   │   ├── testdata/
│   │   │   └── complex_example.toml
│   │   ├── toml.go
│   │   ├── toml_reader.go
│   │   ├── toml_reader_test.go
│   │   ├── toml_writer.go
│   │   └── toml_writer_test.go
│   ├── writer.go
│   ├── xml/
│   │   ├── reader.go
│   │   ├── reader_test.go
│   │   ├── structured_comment_test.go
│   │   ├── writer.go
│   │   ├── writer_internal_test.go
│   │   ├── writer_test.go
│   │   └── xml.go
│   └── yaml/
│       ├── yaml.go
│       ├── yaml_reader.go
│       ├── yaml_test.go
│       └── yaml_writer.go
└── selector/
    ├── README.md
    ├── ast/
    │   ├── ast.go
    │   ├── ast_test.go
    │   ├── expression_complex.go
    │   └── expression_literal.go
    ├── lexer/
    │   ├── token.go
    │   ├── tokenize.go
    │   └── tokenize_test.go
    ├── parser/
    │   ├── denotations.go
    │   ├── error.go
    │   ├── parse_array.go
    │   ├── parse_branch.go
    │   ├── parse_each.go
    │   ├── parse_filter.go
    │   ├── parse_func.go
    │   ├── parse_group.go
    │   ├── parse_if.go
    │   ├── parse_literal.go
    │   ├── parse_map.go
    │   ├── parse_object.go
    │   ├── parse_recursive_descent.go
    │   ├── parse_search.go
    │   ├── parse_sort_by.go
    │   ├── parse_symbol.go
    │   ├── parse_variable.go
    │   ├── parser.go
    │   ├── parser_binary.go
    │   └── parser_test.go
    └── parser.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: TomWright # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
buy_me_a_coffee: TomWright


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: "bug"
assignees: ""
---

**Discussion?**
If this is a question rather than a bug, please raise it in the discussions Q&A section.

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

Include (sanitized) input files and commands to help along the way.

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**

- OS: [e.g. iOS]
- Version [e.g. 22] (`dasel version`)

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: "enhancement"
assignees: ""
---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: "gomod" # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "weekly"


================================================
FILE: .github/workflows/build-dev.yaml
================================================
on:
  push:
    branches:
      - master
      - main
name: Build Dev
jobs:
  publish:
    strategy:
      matrix:
        os:
          - linux
          - darwin
          - windows
        arch:
          - amd64
          - 386
          - arm64
          - arm
        include:
          - os: linux
            arch: amd64
            artifact_name: dasel_linux_amd64
            test_version: true
          - os: linux
            arch: 386
            artifact_name: dasel_linux_386
            test_version: false
          - os: darwin
            arch: amd64
            artifact_name: dasel_darwin_amd64
            test_version: false
          - os: darwin
            arch: arm64
            artifact_name: dasel_darwin_arm64
            test_version: false
          - os: windows
            arch: amd64
            artifact_name: dasel_windows_amd64.exe
            test_version: false
          - os: windows
            arch: 386
            artifact_name: dasel_windows_386.exe
            test_version: false
          - os: linux
            arch: arm64
            artifact_name: dasel_linux_arm64
            test_version: false
          - os: linux
            arch: arm
            artifact_name: dasel_linux_arm32
            test_version: false
        exclude:
          - os: darwin
            arch: 386
          - os: windows
            arch: arm64
          - os: windows
            arch: arm
          - os: darwin
            arch: arm
    name: Dev build ${{ matrix.os }} ${{ matrix.arch }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version: '^1.25.0' # The Go version to download (if necessary) and use.
      - name: Set env
        run: echo RELEASE_VERSION=development >> $GITHUB_ENV
      - name: Build
        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
      - name: Test version
        if: matrix.test_version == true
        run: ./target/release/${{ matrix.artifact_name }} version


================================================
FILE: .github/workflows/build-test.yaml
================================================
on:
  push:
    branches-ignore:
      - master
      - main
  pull_request:
    branches:
      - master
      - main
name: Build Test
jobs:
  publish:
    strategy:
      matrix:
        os:
          - linux
          - darwin
          - windows
        arch:
          - amd64
          - 386
          - arm64
          - arm
        include:
          - os: linux
            arch: amd64
            artifact_name: dasel_linux_amd64
            test_version: true
            test_execution: true
          - os: linux
            arch: 386
            artifact_name: dasel_linux_386
            test_version: false
            test_execution: false
          - os: darwin
            arch: amd64
            artifact_name: dasel_darwin_amd64
            test_version: false
            test_execution: false
          - os: darwin
            arch: arm64
            artifact_name: dasel_darwin_arm64
            test_version: false
            test_execution: false
          - os: windows
            arch: amd64
            artifact_name: dasel_windows_amd64.exe
            test_version: false
            test_execution: false
          - os: windows
            arch: 386
            artifact_name: dasel_windows_386.exe
            test_version: false
            test_execution: false
          - os: linux
            arch: arm64
            artifact_name: dasel_linux_arm64
            test_version: false
            test_execution: false
          - os: linux
            arch: arm
            artifact_name: dasel_linux_arm32
            test_version: false
            test_execution: false
        exclude:
          - os: darwin
            arch: 386
          - os: windows
            arch: arm64
          - os: windows
            arch: arm
          - os: darwin
            arch: arm
    name: Dev build ${{ matrix.os }} ${{ matrix.arch }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version: '^1.25.0' # The Go version to download (if necessary) and use.
      - name: Set env
        run: echo RELEASE_VERSION=development >> $GITHUB_ENV
      - name: Build
        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
      - name: Test version
        if: matrix.test_version == true
        run: ./target/release/${{ matrix.artifact_name }} version
      - name: Test execution
        if: matrix.test_execution == true
        run: |
          echo '{"hello": "World"}' | ./target/release/${{ matrix.artifact_name }} -i json 'hello'


================================================
FILE: .github/workflows/build.yaml
================================================
on:
  push:
    tags:
      - 'v*.*.*'

name: Build
jobs:
  publish:
    strategy:
      matrix:
        os:
          - linux
          - darwin
          - windows
        arch:
          - amd64
          - 386
          - arm64
          - arm
        include:
          - os: linux
            arch: amd64
            artifact_name: dasel_linux_amd64
            asset_name: dasel_linux_amd64
            test_version: true
          - os: linux
            arch: 386
            artifact_name: dasel_linux_386
            asset_name: dasel_linux_386
            test_version: false
          - os: darwin
            arch: amd64
            artifact_name: dasel_darwin_amd64
            asset_name: dasel_darwin_amd64
            test_version: false
          - os: darwin
            arch: arm64
            artifact_name: dasel_darwin_arm64
            asset_name: dasel_darwin_arm64
            test_version: false
          - os: windows
            arch: amd64
            artifact_name: dasel_windows_amd64.exe
            asset_name: dasel_windows_amd64.exe
            test_version: false
          - os: windows
            arch: 386
            artifact_name: dasel_windows_386.exe
            asset_name: dasel_windows_386.exe
            test_version: false
          - os: linux
            arch: arm64
            artifact_name: dasel_linux_arm64
            asset_name: dasel_linux_arm64
            test_version: false
          - os: linux
            arch: arm
            artifact_name: dasel_linux_arm32
            asset_name: dasel_linux_arm32
            test_version: false
        exclude:
            - os: darwin
              arch: 386
            - os: windows
              arch: arm64
            - os: windows
              arch: arm
            - os: darwin
              arch: arm
    name: Build ${{ matrix.os }} ${{ matrix.arch }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version: '^1.25.0'
      - name: Set env
        run: echo RELEASE_VERSION=${GITHUB_REF:10} >> $GITHUB_ENV
      - name: Build
        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
      - name: Test version
        if: matrix.test_version == true
        run: ./target/release/${{ matrix.artifact_name }} version
      - name: Gzip binaries
        run: gzip -c ./target/release/${{ matrix.artifact_name }} > ./target/release/${{ matrix.artifact_name }}.gz
      - name: Upload binaries to release
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: target/release/${{ matrix.artifact_name }}
          asset_name: ${{ matrix.asset_name }}
          tag: ${{ github.ref }}
      - name: Upload gzip binaries to release
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: target/release/${{ matrix.artifact_name }}.gz
          asset_name: ${{ matrix.asset_name }}.gz
          tag: ${{ github.ref }}


================================================
FILE: .github/workflows/bump-homebrew.yaml
================================================
on:
  workflow_run:
    workflows: ["Build"]
    types:
      - completed
name: Bump homebrew
jobs:
  publish:
    name: Update homebrew-core
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - name: Set env
        run: echo "RELEASE_VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
      - name: Homebrew bump formula
        uses: dawidd6/action-homebrew-bump-formula@v7
        with:
          token: ${{ secrets.GH_HOMEBREW_TOKEN }}
          formula: dasel
          tap: homebrew/core
          tag: ${{ env.RELEASE_VERSION }}
          force: true

================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
# ******** NOTE ********

name: "CodeQL"

on:
  push:
    branches: [ master ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ master ]
  schedule:
    - cron: '25 21 * * 1'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        language: [ 'go' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
        # Learn more...
        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection

    steps:
    - name: Checkout repository
      uses: actions/checkout@v6

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        # queries: ./path/to/local/query, your-org/your-repo/queries@main

    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 https://git.io/JvXDl

    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
    #    and modify them (or add more) to build your code if your project
    #    uses a compiled language

    #- run: |
    #   make bootstrap
    #   make release

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2


================================================
FILE: .github/workflows/container.yaml
================================================
on: [push, pull_request]

env:
  GOLANG_VERSION: 1
  IMAGE_NAME: ghcr.io/tomwright/dasel

name: Container build, test and publish
jobs:
  container:
    strategy:
      fail-fast: true
      matrix:
        include:
          - distro: alpine
            distro-tag: latest
          - distro: debian
            distro-tag: bookworm-slim
    runs-on: ubuntu-latest
    steps:
        - name: Checkout
          uses: actions/checkout@v6
        - name: Set up QEMU
          uses: docker/setup-qemu-action@v3
        - name: Set up Docker Buildx
          uses: docker/setup-buildx-action@v3
        - name: Process version tag
          if: ${{ startsWith(github.ref, 'refs/tags/v') }}
          uses: nowsprinting/check-version-format-action@v4
          id: version
          with:
            prefix: 'v'
        - name: Build and Export
          uses: docker/build-push-action@v5
          with:
            context: .
            load: true
            build-args: |
              GOLANG_VERSION=${{ env.GOLANG_VERSION }}
              RELEASE_VERSION=${{ github.ref_name }}
              MAJOR_VERSION=${{ steps.version.outputs.major || 'v3' }}
              TARGET_BASE_IMAGE=${{ matrix.distro }}:${{ matrix.distro-tag }}
            tags: dasel:test
        - name: Test
          run: |
            echo '{"hello": "World"}' | docker run -i --rm dasel:test -i json 'hello'
        - name: Set version tag variables
          if: ${{ steps.version.outputs.is_valid == 'true' }}
          run: |
            IMAGE=${{ env.IMAGE_NAME }}
            MAJOR=${{ steps.version.outputs.major_without_prefix }}
            MINOR=${{ steps.version.outputs.minor }}
            PATCH=${{ steps.version.outputs.patch }}

            if [ "${{ matrix.distro }}" = "alpine" ]; then
              echo "VERSIONED_TAGS<<EOF" >> $GITHUB_ENV
              echo "${IMAGE}:alpine" >> $GITHUB_ENV
              echo "${IMAGE}:${{ github.ref_name }}-alpine" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}-alpine" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}.${MINOR}-alpine" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}.${MINOR}.${PATCH}-alpine" >> $GITHUB_ENV
              echo "EOF" >> $GITHUB_ENV
            else
              echo "VERSIONED_TAGS<<EOF" >> $GITHUB_ENV
              echo "${IMAGE}:latest" >> $GITHUB_ENV
              echo "${IMAGE}:${{ github.ref_name }}" >> $GITHUB_ENV
              echo "${IMAGE}:${{ github.ref_name }}-${{ matrix.distro-tag }}" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}-${{ matrix.distro-tag }}" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}.${MINOR}-${{ matrix.distro-tag }}" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}.${MINOR}.${PATCH}-${{ matrix.distro-tag }}" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}.${MINOR}" >> $GITHUB_ENV
              echo "${IMAGE}:${MAJOR}.${MINOR}.${PATCH}" >> $GITHUB_ENV
              echo "EOF" >> $GITHUB_ENV
            fi
        - name: Login to GitHub Container Registry
          if: ${{ steps.version.outputs.is_valid == 'true' }}
          uses: docker/login-action@v3
          with:
            registry: ghcr.io
            username: TomWright
            password: ${{ secrets.GHCR_PAT }}
        - name: Build and Push
          if: ${{ steps.version.outputs.is_valid == 'true' }}
          uses: docker/build-push-action@v5
          with:
            context: .
            platforms: linux/amd64,linux/arm64
            push: true
            build-args: | 
              GOLANG_VERSION=${{ env.GOLANG_VERSION }}
              RELEASE_VERSION=${{ github.ref_name }}
              MAJOR_VERSION=${{ steps.version.outputs.major }}
              TARGET_BASE_IMAGE=${{ matrix.distro }}:${{ matrix.distro-tag }}
            tags: ${{ env.VERSIONED_TAGS }}


================================================
FILE: .github/workflows/golangci-lint.yaml
================================================
name: golangci-lint
on:
  push:
    branches:
      - main
      - master
  pull_request:

permissions:
  contents: read
  pull-requests: read

jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version: '^1.25.0'
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v8
        with:
          version: v2.4.0

================================================
FILE: .github/workflows/test.yaml
================================================
on: [push, pull_request]
name: Test
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Install Go
        uses: actions/setup-go@v6
        with:
          go-version: '^1.25.0'
      - name: Checkout code
        uses: actions/checkout@v6
      - uses: actions/cache@v4
        with:
          path: ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-
      - name: Test
        run: go test -coverprofile=coverage.txt -covermode=atomic -race ./...
      - uses: codecov/codecov-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
          file: ./coverage.txt # optional
          flags: unittests # optional
          fail_ci_if_error: false # optional (default = false)


================================================
FILE: .gitignore
================================================
.idea/
dasel

================================================
FILE: .golangci.yaml
================================================
version: "2"
linters:
  default: standard
run:
  timeout: 2m

================================================
FILE: .pre-commit-hooks.yaml
================================================
- id: dasel-validate-docker
  name: Validate JSON, YAML, XML, TOML files
  description: Validate JSON files
  language: docker_image
  types_or:
    - json
    - yaml
    - xml
    - toml
  entry: ghcr.io/tomwright/dasel
  args:
    - validate

- id: dasel-validate-bin
  name: Validate JSON, YAML, XML, TOML
  description: Validate JSON, YAML, XML, TOML files
  language: system
  types_or:
    - json
    - yaml
    - xml
    - toml
  entry: dasel
  args:
    - validate

- id: dasel-validate
  name: Validate JSON, YAML, XML, TOML
  description: Validate JSON, YAML, XML, TOML files
  language: golang
  types_or:
    - json
    - yaml
    - xml
    - toml
  entry: dasel
  args:
    - validate


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

- Nothing yet.

## [v3.4.0] - 2026-03-19

### Added

- `keys` func that returns the keys or indices of a node. [See docs](https://daseldocs.tomwright.me/functions/keys).

## [v3.3.2] - 2026-03-18

### Fixed
- Fixed a bug that caused the `get` function to return `false` instead of an error when doing an invalid lookup.
- Fixed an issue with reading/writing null values in YAML.
- Fixed a nil pointer dereference when reading/writing null YAML documents.
- Fixed a security issue allowing unbounded YAML expansion. Thanks to @kq5y.

## [v3.3.1] - 2026-02-26

### Fixed

- Fixed query selector parsing issue that incorrectly parsed array accessors when they followed a `filter` or `map` call.

## [v3.3.0] - 2026-02-25

### Added
- `replace` function to replace occurrences of a substring in a string with another string. [See docs](https://daseldocs.tomwright.me/functions/replace).

## [v3.2.3] - 2026-02-23

### Added
- XML parser now preserves comments and processing instructions during round-trip (#175).

### Fixed
- Spread operator within array construction is now honoured.

## [v3.2.2] - 2026-02-13

### Changed
- Swapped to use goccy/go-json for improved performance. Thanks @imix
- Updated model `IsScalar` internals to improve efficiency. Thanks @imix
- General dependency updates.

### Fixed
- TOML parser sub table parsing. Thanks @pmeier

## [v3.2.1] - 2026-01-05

### Fixed
- XML parser now correctly handles empty CDATA.

## [v3.2.0] - 2025-12-26

### Added
- `join` function to join array elements into a single string with a specified separator. [See docs](https://daseldocs.tomwright.me/functions/join).

## [v3.1.4] - 2025-12-18

### Fixed
- `Select` func in the exposed go API now correctly maps return values to the respective types.

## [v3.1.3] - 2025-12-18

### Fixed
- XML documents no longer create redundant `root` or `item` elements.
- Dasel no longer loses value metadata when reading/writing to internal models.

## [v3.1.2] - 2025-12-17

### Fixed

- Fix XML reading/writing when XML processing instructions are present.

## [v3.1.1] - 2025-12-16

### Fixed

- Homebrew release.

## [v3.1.0] - 2025-12-16

### Added
- `sum` function to sum numeric values in an array. [See docs](https://daseldocs.tomwright.me/functions/sum).

## [v3.0.0] - 2025-12-15

### Added
- Major new version release.
- INI support.
- HCL support.
- Dasel syntax now supports variables and expressions.
- Files can now be read and parsed inside a dasel query.
- Variables can now be passed to dasel from the command line.
- Support for comments in queries.
- Dasel config file to define default file format.
- Interactive mode for dasel CLI (alpha).

### Changed
- Go module path changed to `github.com/tomwright/dasel/v3`.
- Internal changes to support new version.
- Query/selector syntax revamp. See [docs](https://daseldocs.tomwright.me) for more information.
- Majority of read/write operations will now maintain ordering.
- Migrated from Cobra to Kong for CLI parsing/processing.
- Removed `put` and `delete` commands. Instead, modify within the query and use `--root` flag.

### Fixed
- File redirect now works in the same way as piped input.
- Various other bug fixes and improvements.
- Whitespace in query syntax is now handled correctly.

## [v2.8.1] - 2024-06-30

### Fixed
- Fixed a bug related to yaml aliases.

## [v2.8.0] - 2024-06-28

### Fixed

- Fixed a bug that could cause a panic.
- `type()` now returns `null` instead of `unknown` for null values.
- Added YAML support for merge tag/aliases. Thanks to [pmeier](https://github.com/pmeier). [Issue 285](https://github.com/TomWright/dasel/issues/285).

## [v2.7.0] - 2024-03-14

### Added

- `null()` function. [See docs](https://daseldocs.tomwright.me/functions/null)

### Fixed

- Dasel now correctly handles `null` values.

## [v2.6.0] - 2024-02-15

### Added

- Support for `--indent` flag.
- More descriptive errors when dasel fails to open a file.

### Changed

- Docker build improvements in workflows.

## [v2.5.0] - 2023-11-28

### Added

- Add `man` that generates manpages for all dasel subcommands.

### Fixed

- Fixed an issue when [parsing empty input documents](https://github.com/TomWright/dasel/issues/374).

## [v2.4.1] - 2023-10-18

### Fixed

- JSON output now acts as expected regarding the EscapeHTML flag.

## [v2.4.0] - 2023-10-18

### Added

- `orDefault()` function. [See docs](https://daseldocs.tomwright.me/functions/ordefault)
- `--csv-comma` flag to change the csv separator.
- `--csv-write-comma` flag to change the csv separator specifically for writes.
- `--csv-comment` flag to change the csv comment character.
- `--csv-crlf` flag to enable or disable CRLF output when working with csv files.

### Fixed

- Resolved an issue with YAML parser that was causing strings to be read as booleans.
- 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).

## [v2.3.6] - 2023-08-30

### Fixed

- XML is now formatted correctly. (https://github.com/TomWright/dasel/issues/354)

## [v2.3.5] - 2023-08-29

### Changed

- Small internal optimisation (https://github.com/TomWright/dasel/pull/341)
- Update to go 1.21
- Upgrade dependencies

### Fixed

- Resolved an issue with YAML parser that was causing strings to be read as numbers.
- Timestamps can now be resolved as expected in YAML.

## [v2.3.4] - 2023-06-01

### Fixed

- `len` function now works with new map type.
- `keys` function now works with new map type.

## [v2.3.3] - 2023-05-31

### Fixed

- Errors when selecting data are now correctly handled.

## [v2.3.2] - 2023-05-31

### Fixed

- Restored previous octal, binary and hex number parsing support in YAML and `put` command.

## [v2.3.1] - 2023-05-29

### Fixed

- `version` command now outputs correct version information (only affected v2 onwards)

## [v2.3.0] - 2023-05-29

### Changed

- Maps are now ordered internally.
- JSON and YAML maps maintain ordering on read/write.
- `all()` func now works with strings.
- `index()` func now works with strings.

### Fixed

- Multi-document output should now be displayed correctly.
- Index shorthand selector now works with multiple indexes.
- Null values are now correctly handled.

## [v2.2.0] - 2023-04-17

### Added

- `keys()` function.

## [v2.1.2] - 2023-03-27

### Added

- Join function.
- String function.

### Fixed

- Null error caused by null values in arrays. See [PR 307](https://github.com/TomWright/dasel/pull/307).

## [v2.1.1] - 2023-01-19

### Fixed

- Changed go module to `github.com/tomwright/dasel/v3` to ensure it works correctly with go modules.

## [v2.1.0] - 2023-01-11

### Added

- Ability to jump to a parent x levels up with `parent(x)`. Defaults to 1 level.

## [v2.0.2] - 2022-12-07

### Fixed

- Argument parsing issue that caused files to be written to the wrong place. See [discussion 268](https://github.com/TomWright/dasel/discussions/268).

## [v2.0.1] - 2022-12-07

### Added

- `float` type in `put` command.

### Fixed

- Output values are now correctly de-referenced. This fixed issues with encoded values not appearing correctly.
- Escape characters in selector strings now work as expected.

## [v2.0.0] - 2022-12-02

See [documentation](https://daseldocs.tomwright.me) for all changes.

- Selector syntax

## [v1.27.3] - 2022-10-18

### Fixed

- The compact flag now works with the XML parser.

## [v1.27.2] - 2022-10-18

### Fixed

- Help text for select and delete commands now contain all available parsers.
- Errors now implement the `Is` interface so they are easier to use from go.
- 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))

## [v1.27.1] - 2022-09-28

### Fixed

- Improved selector comparison parsing to allow matching on values containing special characters.

## [v1.27.0] - 2022-09-26

### Added

- New `value-file` flag allows you to `put` values read from a file ([Issue 246](https://github.com/TomWright/dasel/issues/246))

## [v1.26.1] - 2022-08-24

### Fixed

- Make the completion command available for use ([Issue 216](https://github.com/TomWright/dasel/issues/216))
- Make the `__complete` command available for use

## [v1.26.0] - 2022-07-09

### Added

- Search optional selector - `(#:key=value)`

## [v1.25.1] - 2022-06-29

### Added

- Pre-commit hooks for validate command.

## [v1.25.0] - 2022-06-26

### Added

- Support for struct type usage in go package.
- Validate command.

## [v1.24.3] - 2022-04-23

### Added

- Gzip compressed binaries on releases.

## [v1.24.2] - 2022-04-22

### Fixed

- 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)

## [v1.24.1] - 2022-03-28

### Changed

- `storage` package has been moved outside the `internal` package.

### Fixed

- New funcs added in `v1.24.0` can now be used as expected since you can now access the `storage.ReadWriteOption`.

## [v1.24.0] - 2022-03-18

### Added

- `Node.NewFromFile` func to load a root node from a file.
- `Node.NewFromReader` func to load a root node from an `io.Reader`.
- `Node.WriteToFile` func to write results to a file.
- `Node.Write` func to write results to an `io.Writer`.

## [v1.23.0] - 2022-03-10

### Fixed

- Update github.com/pelletier/go-toml to consume fix for https://github.com/TomWright/dasel/issues/191.

### Added

- Sprig functions to output formatter template.

## [v1.22.1] - 2021-11-09

### Fixed

- Cleaned up error output

## [v1.22.0] - 2021-11-09

### Added

- Type selector `[@]`.

### Fixed

- Errors are now written to stderr as expected.

## [v1.21.2] - 2021-10-21

### Added

- Linux arm32 build target.

## [v1.21.1] - 2021-09-30

### Changed
- `--escape-html` flag now defaults to false.

## [v1.21.0] - 2021-09-29

### Added
- `--escape-html` flag.

### Fixed
- `put document` and `put object` are now aware of the `--merge-input-documents` flag.

## [v1.20.1] - 2021-09-28

### Added

- `buster-slim` and `alpine` tags to built docker images.

### Fixed

- Different encodings in XML files are now [handled as expected](https://github.com/TomWright/dasel/issues/164).

## [v1.20.0] - 2021-08-30

### Added

- `-v`, `--value` flag to workaround [dash issue](https://github.com/TomWright/dasel/issues/117).

### Fixed

- Fixed an issue in which unicode characters could cause issues when parsing selectors.

## [v1.19.0] - 2021-08-14

### Added

- `--colour`,`--color` flag to enable colourised output in select command.

## [v1.18.0] - 2021-08-11

### Added

- `--format` flag to `select` command.

## [v1.17.0] - 2021-08-08

### Added

- Support for `!=` comparison operator in dynamic and search selectors.
- Support for `-`/`keyValue` key in dynamic selectors.

## [v1.16.1] - 2021-08-02

### Fixed

- Fixed a bug that stopped the delete command editing files in place.

## [v1.16.0] - 2021-08-01

### Added

- Delete command.

## [v1.15.0] - 2021-05-06

### Added

- `--merge-input-documents` flag.

### Changed

- Optional `noupdater` build tag to disable the self-update command.

### Fixed

- Empty XML documents are now parsed correctly.
  - https://github.com/TomWright/dasel/issues/131

## [v1.14.1] - 2021-04-15

### Added

- arm64 build support.

## [v1.14.0] - 2021-04-11

### Added

- `.[#]` length selector.
- `>` comparison operator.
- `>=` comparison operator.
- `<` comparison operator.
- `<=` comparison operator.

## [v1.13.6] - 2021-03-29

### Changed

- Development versions of dasel will now include more specific version information where possible.

### Fixed

- Fix an issue that stopped dasel being able to output CSV documents when parsed from JSON.

## [v1.13.5] - 2021-03-22

### Fixed

- Empty map values are now initialised as `map[string]interface{}` rather than `map[interface{}]interface{}`.

## [v1.13.4] - 2021-03-11

### Fixed

- Empty document input is now treated different in select and put commands.
  - https://github.com/TomWright/dasel/issues/99
  - https://github.com/TomWright/dasel/issues/102

## [v1.13.3] - 2021-03-05

### Fixed

- Blank YAML and CSV input is now treated as an empty document.

### Changed

- Blank JSON input is now treated as an empty document.

## [v1.13.2] - 2021-02-25

### Changed

- Improved information provided in `UnsupportedTypeForSelector` errors.
- Upgrade to go 1.16.

### Fixed

- Make sure the `-n`,`--null` flag has an effect in multi-select queries.

## [v1.13.1] - 2021-02-18

### Fixed

- Added `CGO_ENABLED=0` build flag to ensure linux_amd64 builds are statically linked.

## [v1.13.0] - 2021-02-11

### Added

- `--length` flag to select command.

## [v1.12.2] - 2021-01-05

### Fixed

- Fix a bug that stopped the write parser being properly detected when writing to the input file.

## [v1.12.1] - 2021-01-05

### Changed

- Build workflows now updated to run on ubuntu-latest and use a matrix to build assets for `linux`, `darwin` and
  `windows` for both `amd64` and `386`.

### Fixed

- Release asset for macos/darwin is now named `dasel_darwin_amd64` instead of `dasel_macos_amd64`.
- Self-updater now identifies `dev` version as development.

## [v1.12.0] - 2021-01-02

### Added

- Add `-c`, `--compact` flag to remove pretty-print formatting from JSON output.
- Defined `storage.IndentOption(indent string) ReadWriteOption`.
- Defined `storage.PrettyPrintOption(enabled bool) ReadWriteOption`.

### Changed

- Changed `storage.Parser` funcs to allow the passing of `...ReadWriteOption`.

## [v1.11.0] - 2020-12-22

### Added

- Benchmark info now contains graphs.
- `update` command to self-update dasel.

### Changed

- Benchmark info now directly compares dasel, jq and yq.

## [v1.10.0] - 2020-12-19

### Added

- Add `dasel put document` command.
- Benchmark information.

### Fixed

- `-r`,`--read` and `-w`,`--write` flags are now used in `dasel put object`.
- Fix issues that occurred when writing to the root node.

### Changed

- Command names and descriptions.

## [v1.9.1] - 2020-12-12

### Fixed

- Stopped parsing XML entities in strings.

## [v1.9.0] - 2020-12-12

### Added

- Add keys/index selector in multi queries.
- Add `-n`,`--null` flag.

## [v1.8.0] - 2020-12-01

### Added

- Add ability to use `ANY_INDEX` (`[*]`) and `DYNAMIC` (`(x=y)`) selectors on maps/objects.

## [v1.7.0] - 2020-11-30

### Added

- Add `-r`,`--read` and `-w`,`--write` flags to specifically choose input/output parsers. This allows you to convert data between formats.

## [v1.6.2] - 2020-11-18

### Added

- Add support for multi-document JSON files.

## [v1.6.1] - 2020-11-17

### Changed

- Remove some validation on `dasel put object` to allow you to put empty objects.

## [v1.6.0] - 2020-11-17

### Added

- Add search selector to allow recursive searching from the current node.

## [v1.5.1] - 2020-11-14

### Fixed

- Fixed an issue that stopped new values being saved.

## [v1.5.0] - 2020-11-12

### Added

- Add ability to use `\` as an escape character in selectors.

## [v1.4.1] - 2020-11-11

### Fixed

- Fix an issue when parsing dynamic selectors.

## [v1.4.0] - 2020-11-08

### Added

- Add `-m`,`--multiple` flag to deal with multi-value queries.
- Add `ANY_INDEX` or `[*]` selector.
- Add `NextMultiple` property to the `Node` struct - this is used when processing multi-value queries.
- Add `Node.QueryMultiple` func.
- Add `Node.PutMultiple` func.

## [v1.3.0] - 2020-11-08

### Added

- Add support for CSV files.

## [v1.2.0] - 2020-11-07

### Added

- Add support for multi-document YAML files.
- Add CodeQL step in github actions.

### Changed

- Docker image is now pushed to ghcr instead of github packages.

## [v1.1.0] - 2020-11-01

### Added

- Add sub-selector support in dynamic selectors.

## [v1.0.4] - 2020-10-30

### Added

- Add `--plain` flag to tell dasel to output un-formatted values.

## [v1.0.3] - 2020-10-29

### Changed

- Command output is now followed by a newline.

## [v1.0.2] - 2020-10-28

### Added

- Docker image is now built and pushed when a new release is tagged.

## [v1.0.1] - 2020-10-28

### Added

- Add support for XML.

### Changed

- Add `-` as an alias for `stdin`/`stdout` in `--file` and `--output` flags.
- Selector can now be given as the first argument making the flag itself optional.
- `select` is now the default command.

## [v1.0.0] - 2020-10-27

### Added

- Add lots of tests.
- Add docs.
- Got accepted to go-awesome.

## [v0.0.5] - 2020-09-27

### Added

- Add support for TOML.

## [v0.0.4] - 2020-09-27

### Added

- Ability to check against the node value in a dynamic selector.
- Code coverage.

### Changed

- Use reflection instead of fixed type checks.

## [v0.0.3] - 2020-09-24

### Changed

- Use reflection instead of fixed type checks.
- Extract commands into their own functions to make them testable.

## [v0.0.2] - 2020-09-23

### Added

- Add ability to pipe data in/out of dasel.
- Add dasel put command.

## [v0.0.1] - 2020-09-22

### Added

- Everything!

[unreleased]: https://github.com/TomWright/dasel/compare/v3.4.0...HEAD
[v3.4.0]: https://github.com/TomWright/dasel/compare/v3.3.2...v3.4.0
[v3.3.2]: https://github.com/TomWright/dasel/compare/v3.3.1...v3.3.2
[v3.3.1]: https://github.com/TomWright/dasel/compare/v3.3.0...v3.3.1
[v3.3.0]: https://github.com/TomWright/dasel/compare/v3.2.3...v3.3.0
[v3.2.3]: https://github.com/TomWright/dasel/compare/v3.2.2...v3.2.3
[v3.2.2]: https://github.com/TomWright/dasel/compare/v3.2.1...v3.2.2
[v3.2.1]: https://github.com/TomWright/dasel/compare/v3.2.0...v3.2.1
[v3.2.0]: https://github.com/TomWright/dasel/compare/v3.1.4...v3.2.0
[v3.1.4]: https://github.com/TomWright/dasel/compare/v3.1.3...v3.1.4
[v3.1.3]: https://github.com/TomWright/dasel/compare/v3.1.2...v3.1.3
[v3.1.2]: https://github.com/TomWright/dasel/compare/v3.1.1...v3.1.2
[v3.1.1]: https://github.com/TomWright/dasel/compare/v3.1.0...v3.1.1
[v3.1.0]: https://github.com/TomWright/dasel/compare/v3.0.0...v3.1.0
[v3.0.0]: https://github.com/TomWright/dasel/compare/v2.8.1...v3.0.0
[v2.8.1]: https://github.com/TomWright/dasel/compare/v2.8.0...v2.8.1
[v2.8.0]: https://github.com/TomWright/dasel/compare/v2.7.0...v2.8.0
[v2.7.0]: https://github.com/TomWright/dasel/compare/v2.6.0...v2.7.0
[v2.6.0]: https://github.com/TomWright/dasel/compare/v2.5.0...v2.6.0
[v2.5.0]: https://github.com/TomWright/dasel/compare/v2.4.1...v2.5.0
[v2.4.1]: https://github.com/TomWright/dasel/compare/v2.4.0...v2.4.1
[v2.4.0]: https://github.com/TomWright/dasel/compare/v2.3.6...v2.4.0
[v2.3.6]: https://github.com/TomWright/dasel/compare/v2.3.5...v2.3.6
[v2.3.5]: https://github.com/TomWright/dasel/compare/v2.3.4...v2.3.5
[v2.3.4]: https://github.com/TomWright/dasel/compare/v2.3.3...v2.3.4
[v2.3.3]: https://github.com/TomWright/dasel/compare/v2.3.2...v2.3.3
[v2.3.2]: https://github.com/TomWright/dasel/compare/v2.3.1...v2.3.2
[v2.3.1]: https://github.com/TomWright/dasel/compare/v2.3.0...v2.3.1
[v2.3.0]: https://github.com/TomWright/dasel/compare/v2.2.0...v2.3.0
[v2.2.0]: https://github.com/TomWright/dasel/compare/v2.1.2...v2.2.0
[v2.1.2]: https://github.com/TomWright/dasel/compare/v2.1.1...v2.1.2
[v2.1.1]: https://github.com/TomWright/dasel/compare/v2.1.0...v2.1.1
[v2.1.0]: https://github.com/TomWright/dasel/compare/v2.0.2...v2.1.0
[v2.0.2]: https://github.com/TomWright/dasel/compare/v2.0.1...v2.0.2
[v2.0.1]: https://github.com/TomWright/dasel/compare/v2.0.0...v2.0.1
[v2.0.0]: https://github.com/TomWright/dasel/compare/v1.27.3...v2.0.0
[v1.27.3]: https://github.com/TomWright/dasel/compare/v1.27.2...v1.27.3
[v1.27.2]: https://github.com/TomWright/dasel/compare/v1.27.1...v1.27.2
[v1.27.1]: https://github.com/TomWright/dasel/compare/v1.27.0...v1.27.1
[v1.27.0]: https://github.com/TomWright/dasel/compare/v1.26.1...v1.27.0
[v1.26.1]: https://github.com/TomWright/dasel/compare/v1.26.0...v1.26.1
[v1.26.0]: https://github.com/TomWright/dasel/compare/v1.25.1...v1.26.0
[v1.25.1]: https://github.com/TomWright/dasel/compare/v1.25.0...v1.25.1
[v1.25.0]: https://github.com/TomWright/dasel/compare/v1.24.3...v1.25.0
[v1.24.3]: https://github.com/TomWright/dasel/compare/v1.24.2...v1.24.3
[v1.24.2]: https://github.com/TomWright/dasel/compare/v1.24.1...v1.24.2
[v1.24.1]: https://github.com/TomWright/dasel/compare/v1.24.0...v1.24.1
[v1.24.0]: https://github.com/TomWright/dasel/compare/v1.23.0...v1.24.0
[v1.23.0]: https://github.com/TomWright/dasel/compare/v1.22.1...v1.23.0
[v1.22.1]: https://github.com/TomWright/dasel/compare/v1.22.0...v1.22.1
[v1.22.0]: https://github.com/TomWright/dasel/compare/v1.21.2...v1.22.0
[v1.21.2]: https://github.com/TomWright/dasel/compare/v1.21.1...v1.21.2
[v1.21.1]: https://github.com/TomWright/dasel/compare/v1.21.0...v1.21.1
[v1.21.0]: https://github.com/TomWright/dasel/compare/v1.20.1...v1.21.0
[v1.20.1]: https://github.com/TomWright/dasel/compare/v1.20.0...v1.20.1
[v1.20.0]: https://github.com/TomWright/dasel/compare/v1.19.0...v1.20.0
[v1.19.0]: https://github.com/TomWright/dasel/compare/v1.18.0...v1.19.0
[v1.18.0]: https://github.com/TomWright/dasel/compare/v1.17.0...v1.18.0
[v1.17.0]: https://github.com/TomWright/dasel/compare/v1.16.1...v1.17.0
[v1.16.1]: https://github.com/TomWright/dasel/compare/v1.16.0...v1.16.1
[v1.16.0]: https://github.com/TomWright/dasel/compare/v1.15.0...v1.16.0
[v1.15.0]: https://github.com/TomWright/dasel/compare/v1.14.1...v1.15.0
[v1.14.1]: https://github.com/TomWright/dasel/compare/v1.14.0...v1.14.1
[v1.14.0]: https://github.com/TomWright/dasel/compare/v1.13.6...v1.14.0
[v1.13.6]: https://github.com/TomWright/dasel/compare/v1.13.5...v1.13.6
[v1.13.5]: https://github.com/TomWright/dasel/compare/v1.13.4...v1.13.5
[v1.13.4]: https://github.com/TomWright/dasel/compare/v1.13.3...v1.13.4
[v1.13.3]: https://github.com/TomWright/dasel/compare/v1.13.2...v1.13.3
[v1.13.2]: https://github.com/TomWright/dasel/compare/v1.13.1...v1.13.2
[v1.13.1]: https://github.com/TomWright/dasel/compare/v1.13.0...v1.13.1
[v1.13.0]: https://github.com/TomWright/dasel/compare/v1.12.2...v1.13.0
[v1.12.2]: https://github.com/TomWright/dasel/compare/v1.12.1...v1.12.2
[v1.12.1]: https://github.com/TomWright/dasel/compare/v1.12.0...v1.12.1
[v1.12.0]: https://github.com/TomWright/dasel/compare/v1.11.0...v1.12.0
[v1.11.0]: https://github.com/TomWright/dasel/compare/v1.10.0...v1.11.0
[v1.10.0]: https://github.com/TomWright/dasel/compare/v1.9.1...v1.10.0
[v1.9.1]: https://github.com/TomWright/dasel/compare/v1.9.0...v1.9.1
[v1.9.0]: https://github.com/TomWright/dasel/compare/v1.8.0...v1.9.0
[v1.8.0]: https://github.com/TomWright/dasel/compare/v1.7.0...v1.8.0
[v1.7.0]: https://github.com/TomWright/dasel/compare/v1.6.2...v1.7.0
[v1.6.2]: https://github.com/TomWright/dasel/compare/v1.6.1...v1.6.2
[v1.6.1]: https://github.com/TomWright/dasel/compare/v1.6.0...v1.6.1
[v1.6.0]: https://github.com/TomWright/dasel/compare/v1.5.1...v1.6.0
[v1.5.1]: https://github.com/TomWright/dasel/compare/v1.5.0...v1.5.1
[v1.5.0]: https://github.com/TomWright/dasel/compare/v1.4.1...v1.5.0
[v1.4.1]: https://github.com/TomWright/dasel/compare/v1.4.0...v1.4.1
[v1.4.0]: https://github.com/TomWright/dasel/compare/v1.3.0...v1.4.0
[v1.3.0]: https://github.com/TomWright/dasel/compare/v1.2.0...v1.3.0
[v1.1.0]: https://github.com/TomWright/dasel/compare/v1.0.4...v1.1.0
[v1.0.4]: https://github.com/TomWright/dasel/compare/v1.0.3...v1.0.4
[v1.0.3]: https://github.com/TomWright/dasel/compare/v1.0.2...v1.0.3
[v1.0.2]: https://github.com/TomWright/dasel/compare/v1.0.1...v1.0.2
[v1.0.1]: https://github.com/TomWright/dasel/compare/v1.0.0...v1.0.1
[v1.0.0]: https://github.com/TomWright/dasel/compare/v0.0.5...v1.0.0
[v0.0.5]: https://github.com/TomWright/dasel/compare/v0.0.4...v0.0.5
[v0.0.4]: https://github.com/TomWright/dasel/compare/v0.0.3...v0.0.4
[v0.0.3]: https://github.com/TomWright/dasel/compare/v0.0.2...v0.0.3
[v0.0.2]: https://github.com/TomWright/dasel/compare/v0.0.1...v0.0.2
[v0.0.1]: https://github.com/TomWright/dasel/releases/tag/v0.0.1

================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

- The use of sexualized language or imagery and unwelcome sexual attention or
  advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
  address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at contact@tomwright.me. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Dasel

Thank you for considering contributing to Dasel! Contributions of all kinds are welcome — whether it's fixing bugs, improving documentation, or adding new features.

## How to Contribute

### 1. Reporting Issues

* Check the [issue tracker](https://github.com/TomWright/dasel/issues) to see if your issue has already been reported.
* If not, open a new issue with a clear description. Please include:

    * Steps to reproduce (if it's a bug)
    * Expected vs actual behavior
    * Versions of Dasel, Go, and your OS

### 2. Suggesting Features

* Open a [discussion](https://github.com/TomWright/dasel/discussions) if you'd like feedback before implementing.
* If the idea is well-defined, create an issue describing the use case and possible syntax.

### 3. Submitting Pull Requests

1. Fork the repository and clone your fork.
2. Create a new branch for your work:

   ```bash
   git checkout -b feature/my-new-feature
   ```
3. Make your changes and add tests if relevant.
4. Run the test suite to ensure nothing is broken:

   ```bash
   go test ./...
   ```
5. Commit your changes with a clear message:

   ```bash
   git commit -m "Add support for XYZ selector"
   ```
6. Push your branch and open a Pull Request.

### 4. Code Style

* Follow Go best practices and conventions.
* Keep code simple and readable.
* Add comments for complex logic.

### 5. Documentation

* Ensure documentation requirements are listed on your PR so docs site can be updated.
* Ensure examples are clear and consistent with the style of existing docs.

### 6. Communication

* Be respectful and constructive in discussions.
* Aim to keep contributions focused and incremental.

---

## Getting Help

If you have questions, feel free to:

* Start a [discussion](https://github.com/TomWright/dasel/discussions)
* Ask in an open issue related to your question

We appreciate your contribution and for helping improve Dasel!


================================================
FILE: Dockerfile
================================================
ARG GOLANG_VERSION=1.25.0
ARG TARGET_BASE_IMAGE=debian:bookworm-slim
FROM golang:${GOLANG_VERSION} AS builder

ARG MAJOR_VERSION=v3
ARG RELEASE_VERSION=master
ARG CGO_ENABLED=0

COPY . .

RUN go build -o /dasel -ldflags="-w -s -X 'github.com/tomwright/dasel/${MAJOR_VERSION}/internal.Version=${RELEASE_VERSION}'" ./cmd/dasel

FROM ${TARGET_BASE_IMAGE}

COPY --from=builder --chmod=755 /dasel /usr/local/bin/dasel

ENTRYPOINT ["/usr/local/bin/dasel"]
CMD ["--help"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Tom Wright

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
[![Gitbook](https://badges.aleen42.com/src/gitbook_1.svg)](https://daseldocs.tomwright.me)
[![Go Report Card](https://goreportcard.com/badge/github.com/tomwright/dasel/v3)](https://goreportcard.com/report/github.com/tomwright/dasel/v3)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/tomwright/dasel)](https://pkg.go.dev/github.com/tomwright/dasel/v3)
![Test](https://github.com/TomWright/dasel/workflows/Test/badge.svg)
![Build](https://github.com/TomWright/dasel/workflows/Build/badge.svg)
[![codecov](https://codecov.io/gh/TomWright/dasel/branch/master/graph/badge.svg)](https://codecov.io/gh/TomWright/dasel)
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)
![GitHub Downloads](https://img.shields.io/github/downloads/TomWright/dasel/total)
![Homebrew Formula Downloads](https://img.shields.io/homebrew/installs/dy/dasel?label=brew%20installs)
![GitHub License](https://img.shields.io/github/license/TomWright/dasel)
[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/TomWright/dasel?label=latest%20release)](https://github.com/TomWright/dasel/releases/latest)
[![Homebrew tag (latest by date)](https://img.shields.io/homebrew/v/dasel)](https://formulae.brew.sh/formula/dasel)

<div align="center">
    <img src="./daselgopher.png" alt="Dasel mascot" width="250"/>
</div>

# Dasel

Dasel (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.

It provides a consistent, powerful syntax to traverse and update data — making it useful for developers, DevOps, and data wrangling tasks.

---

## Features

* **Multi-format support**: JSON, YAML, TOML, XML, CSV, HCL, INI.
* **Unified query syntax**: Access data in any format with the same selectors.
* **Query & search**: Extract values, lists, or structures with intuitive syntax.
* **Modify in place**: Update, insert, or delete values directly in structured files.
* **Convert between formats**: Seamlessly transform data from JSON → YAML, TOML → JSON, etc.
* **Script-friendly**: Simple CLI integration for shell scripts and pipelines.
* **Library support**: Import and use in Go projects.

---

## Installation

### Homebrew (macOS/Linux)

```sh
brew install dasel
```

### Go Install

```sh
go install github.com/tomwright/dasel/v3/cmd/dasel@master
```

### Prebuilt Binaries

Prebuilt binaries are available on the [Releases](https://github.com/TomWright/dasel/releases) page for Linux, macOS, and Windows.

### None of the above?

See the [installation docs](https://daseldocs.tomwright.me/getting-started/installation) for more options.

---

## Basic Usage

### Selecting Values

By default, Dasel evaluates the final selector and prints the result.

```sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json 'foo.bar'
# Output: "baz"
```

### Modifying Values

Update values inline:

```sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json 'foo.bar = "bong"'
# Output: "bong"
```

Use `--root` to output the full document after modification:

```sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json --root 'foo.bar = "bong"'
# Output:
{
  "foo": {
    "bar": "bong"
  }
}
```

Update values based on previous value:

```sh
echo '[1,2,3,4,5]' | dasel -i json --root 'each($this = $this*2)'
# Output:
[
    2,
    4,
    6,
    8,
    10
]
```

### Format Conversion

```sh
cat data.json | dasel -i json -o yaml
```

### Recursive Descent (`..`)

Searches all nested objects and arrays for a matching key or index.

```sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json '..bar'
# Output:
[
    "baz"
]

```

### Search (`search`)

Finds all values matching a condition anywhere in the structure.

```sh
echo '{"foo": {"bar": "baz"}}' | dasel -i json 'search(bar == "baz")'
# Output:
[
    {
        "bar": "baz"
    }
]

```

---

## Documentation

Full documentation is available at [daseldocs.tomwright.me](https://daseldocs.tomwright.me).

---

## Contributing

Contributions are welcome! Please see the [CONTRIBUTING.md](./CONTRIBUTING.md) for details.

---

## License

MIT License. See [LICENSE](./LICENSE) for details.

## Stargazers over time

[![Stargazers over time](https://starchart.cc/TomWright/dasel.svg)](https://starchart.cc/TomWright/dasel)


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

Only the latest major version of Dasel is currently supported with security updates.

| Version | Supported          |
| ------- | ------------------ |
| 3.x.x   | :white_check_mark: |
| 2.x.x   | :x:                |
| 1.x.x   | :x:                |

## Reporting a Vulnerability

If you believe you have found a security vulnerability in Dasel, please report it privately and directly via one of the following:

- [GitHub vulnerability submission](https://github.com/TomWright/dasel/security/advisories/new)
- [contact@tomwright.me](mailto:contact@tomwright.me)

Please do **not** create a public GitHub issue for security vulnerabilities.  
This allows proper investigation and remediation before disclosure.

### What to Expect

- You will receive an acknowledgement of your report within **7 days**.
- If the vulnerability is confirmed, we will work to release a fix as quickly as possible and will notify you once resolved.
- If the report is declined, we will provide reasoning where possible.

### Important Notes

Security vulnerabilities are not the same as bugs, feature requests, or integration issues.  
For non-security bugs, please use the standard GitHub issue tracker:

👉 https://github.com/TomWright/dasel/issues  

Thank you for helping keep Dasel and its users safe.


================================================
FILE: api.go
================================================
// Package dasel contains everything you'll need to use dasel from a go application.
package dasel

import (
	"context"
	"github.com/tomwright/dasel/v3/execution"
	"github.com/tomwright/dasel/v3/model"
)

// Query queries the data using the selector and returns the results.
func Query(ctx context.Context, data any, selector string, opts ...execution.ExecuteOptionFn) ([]*model.Value, int, error) {
	options := execution.NewOptions(opts...)
	val := model.NewValue(data)
	out, err := execution.ExecuteSelector(ctx, selector, val, options)
	if err != nil {
		return nil, 0, err
	}

	if out.IsBranch() || out.IsSpread() {
		res := make([]*model.Value, 0)
		if err := out.RangeSlice(func(i int, v *model.Value) error {
			res = append(res, v)
			return nil
		}); err != nil {
			return nil, 0, err
		}
		return res, len(res), nil
	}

	return []*model.Value{out}, 1, nil
}

// Select queries the data using the selector and returns the results as native Go types.
// Ordering within maps is not guaranteed.
func Select(ctx context.Context, data any, selector string, opts ...execution.ExecuteOptionFn) (any, int, error) {
	res, count, err := Query(ctx, data, selector, opts...)
	if err != nil {
		return nil, 0, err
	}
	out := make([]any, 0)
	for _, v := range res {
		goValue, err := v.GoValue()
		if err != nil {
			return nil, 0, err
		}
		out = append(out, goValue)
	}
	return out, count, err
}

// Modify runs the query against the given data and updates it in-place.
// Given data must be a pointer to a mutable data structure.
func Modify(ctx context.Context, data any, selector string, newValue any, opts ...execution.ExecuteOptionFn) (int, error) {
	res, count, err := Query(ctx, data, selector, opts...)
	if err != nil {
		return 0, err
	}
	for _, v := range res {
		if err := v.Set(model.NewValue(newValue)); err != nil {
			return 0, err
		}
	}
	return count, nil
}


================================================
FILE: api_example_test.go
================================================
package dasel_test

import (
	"context"
	"fmt"
	"github.com/tomwright/dasel/v3"
	"github.com/tomwright/dasel/v3/execution"
)

func ExampleSelect() {
	myData := map[string]any{
		"users": []map[string]any{
			{"name": "Alice", "age": 30},
			{"name": "Bob", "age": 25},
			{"name": "Tom", "age": 40},
		},
	}
	query := `users.filter(age > 27).map(name)...`
	selectResult, numResults, err := dasel.Select(context.Background(), myData, query, execution.WithUnstable())
	if err != nil {
		panic(err)
	}
	fmt.Printf("Found %d results:\n", numResults)

	// You should validate the type assertion in real code.
	selectResults := selectResult.([]any)

	// Results can be of various types, handle accordingly.
	for _, result := range selectResults {
		fmt.Println(result)
	}

	// Output:
	// Found 2 results:
	// Alice
	// Tom
}


================================================
FILE: api_test.go
================================================
package dasel_test

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/tomwright/dasel/v3"
)

type modifyTestCase struct {
	selector string
	in       any
	value    any
	exp      any
	count    int
}

func (tc modifyTestCase) run(t *testing.T) {
	count, err := dasel.Modify(t.Context(), &tc.in, tc.selector, tc.value)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if count != tc.count {
		t.Errorf("unexpected count: %d", count)
	}
	if !cmp.Equal(tc.exp, tc.in) {
		t.Errorf("unexpected result: %s", cmp.Diff(tc.exp, tc.in))
	}
}

func TestQuery(t *testing.T) {
	t.Run("basic query", func(t *testing.T) {
		inputData := map[string]any{
			"users": []map[string]any{
				{"name": "Alice", "age": 30},
				{"name": "Bob", "age": 25},
			},
		}
		results, count, err := dasel.Query(t.Context(), inputData, "users.map(name)...")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if count != 2 {
			t.Errorf("unexpected count: %d", count)
		}
		exp := []string{"Alice", "Bob"}
		for i, r := range results {
			strVal, err := r.StringValue()
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if strVal != exp[i] {
				t.Errorf("unexpected result at index %d: %s", i, strVal)
			}
		}
	})
}

func TestSelect(t *testing.T) {
	t.Run("basic select", func(t *testing.T) {
		inputData := map[string]any{
			"users": []map[string]any{
				{"name": "Alice", "age": 30},
				{"name": "Bob", "age": 25},
			},
		}
		result, count, err := dasel.Select(t.Context(), inputData, "users.map(name)...")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if count != 2 {
			t.Errorf("unexpected count: %d", count)
		}
		exp := []any{"Alice", "Bob"}
		if !cmp.Equal(exp, result) {
			t.Errorf("unexpected result: %s", cmp.Diff(exp, result))
		}
	})
}

func TestModify(t *testing.T) {
	t.Run("index", func(t *testing.T) {
		t.Run("int over int", modifyTestCase{
			selector: "$this[1]",
			in:       []int{1, 2, 3},
			value:    4,
			exp:      []int{1, 4, 3},
			count:    1,
		}.run)
		t.Run("string over int", modifyTestCase{
			selector: "$this[1]",
			in:       []any{1, 2, 3},
			value:    "4",
			exp:      []any{1, "4", 3},
			count:    1,
		}.run)
	})
}


================================================
FILE: codecov.yaml
================================================
comment: no # do not comment PR with the result

coverage:
  range: 50..90 # coverage lower than 50 is red, higher than 90 green, between color code

  status:
    project: # settings affecting project coverage
      default:
        target: auto # auto % coverage target
        threshold: 5%  # allow for 5% reduction of coverage without failing

    # do not run coverage on patch nor changes
    patch: false

================================================
FILE: execution/README.md
================================================
# Execution

The execution package accepts a `model.Value`, parses a selector and executes the resulting AST on the value.


================================================
FILE: execution/context.go
================================================
package execution

import (
	"context"
	"fmt"
)

type ctxKey string

const (
	executorIDCtxKey    ctxKey = "executorID"
	executorPathCtxKey  ctxKey = "executorPath"
	executorDepthCtxKey ctxKey = "executorDepth"
)

func WithExecutorID(ctx context.Context, executorID string) context.Context {
	currentPath := ExecutorPath(ctx)
	newPath := fmt.Sprintf("%s/%s", currentPath, executorID)
	currentDepth := ExecutorDepth(ctx)
	newDepth := currentDepth + 1
	ctx = context.WithValue(ctx, executorIDCtxKey, executorID)
	ctx = context.WithValue(ctx, executorPathCtxKey, newPath)
	ctx = context.WithValue(ctx, executorDepthCtxKey, newDepth)
	return ctx
}

func ExecutorID(ctx context.Context) string {
	v, ok := ctx.Value(executorIDCtxKey).(string)
	if !ok {
		return ""
	}
	return v
}

func ExecutorPath(ctx context.Context) string {
	v, ok := ctx.Value(executorPathCtxKey).(string)
	if !ok {
		return ""
	}
	return v
}

func ExecutorDepth(ctx context.Context) int {
	v, ok := ctx.Value(executorDepthCtxKey).(int)
	if !ok {
		return 0
	}
	return v
}


================================================
FILE: execution/execute.go
================================================
package execution

import (
	"context"
	"errors"
	"fmt"
	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector"
	"github.com/tomwright/dasel/v3/selector/ast"
	"os"
	"reflect"
	"slices"
)

// ExecuteSelector parses the selector and executes the resulting AST with the given input.
func ExecuteSelector(ctx context.Context, selectorStr string, value *model.Value, opts *Options) (*model.Value, error) {
	if selectorStr == "" {
		return value, nil
	}

	expr, err := selector.Parse(selectorStr)
	if err != nil {
		return nil, fmt.Errorf("error parsing selector: %w", err)
	}

	res, err := ExecuteAST(ctx, expr, value, opts)
	if err != nil {
		return nil, fmt.Errorf("error executing selector: %w", err)
	}

	return res, nil
}

type expressionExecutor func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error)

// ExecuteAST executes the given AST with the given input.
func ExecuteAST(ctx context.Context, expr ast.Expr, value *model.Value, options *Options) (*model.Value, error) {
	if expr == nil {
		return value, nil
	}

	executorFn, err := exprExecutor(options, expr)
	if err != nil {
		return nil, fmt.Errorf("error evaluating expression %T: %w", expr, err)
	}

	executor := func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		options.Vars["this"] = data
		out, err := executorFn(ctx, options, data)
		if err != nil {
			return out, err
		}
		return out, nil
	}

	if !value.IsBranch() {
		res, err := executor(ctx, options, value)
		if err != nil {
			return nil, fmt.Errorf("execution error when processing %T: %w", expr, err)
		}
		return res, nil
	}

	res := model.NewSliceValue()
	res.MarkAsBranch()

	if err := value.RangeSlice(func(i int, v *model.Value) error {
		r, err := executor(ctx, options, v)
		if err != nil {
			return err
		}
		if r.IsIgnore() {
			return nil
		}
		return res.Append(r)
	}); err != nil {
		return nil, fmt.Errorf("branch execution error when processing %T: %w", expr, err)
	}

	return res, nil
}

var unstableAstTypes = []reflect.Type{
	reflect.TypeFor[ast.BranchExpr](),
}

func exprExecutor(options *Options, expr ast.Expr) (expressionExecutor, error) {
	if !options.Unstable && (slices.Contains(unstableAstTypes, reflect.TypeOf(expr)) ||
		slices.Contains(unstableAstTypes, reflect.ValueOf(expr).Type())) {
		return nil, errors.New("unstable ast types are not enabled. to enable them use --unstable")
	}

	switch e := expr.(type) {
	case ast.BinaryExpr:
		return binaryExprExecutor(e)
	case ast.UnaryExpr:
		return unaryExprExecutor(e)
	case ast.CallExpr:
		return callExprExecutor(options, e)
	case ast.ChainedExpr:
		return chainedExprExecutor(e)
	case ast.SpreadExpr:
		return spreadExprExecutor()
	case ast.RangeExpr:
		return rangeExprExecutor(e)
	case ast.IndexExpr:
		return indexExprExecutor(e)
	case ast.PropertyExpr:
		return propertyExprExecutor(e)
	case ast.VariableExpr:
		return variableExprExecutor(e)
	case ast.NumberIntExpr:
		return numberIntExprExecutor(e)
	case ast.NumberFloatExpr:
		return numberFloatExprExecutor(e)
	case ast.StringExpr:
		return stringExprExecutor(e)
	case ast.BoolExpr:
		return boolExprExecutor(e)
	case ast.ObjectExpr:
		return objectExprExecutor(e)
	case ast.MapExpr:
		return mapExprExecutor(e)
	case ast.EachExpr:
		return eachExprExecutor(e)
	case ast.FilterExpr:
		return filterExprExecutor(e)
	case ast.SearchExpr:
		return searchExprExecutor(e)
	case ast.RecursiveDescentExpr:
		return recursiveDescentExprExecutor2(e)
	case ast.ConditionalExpr:
		return conditionalExprExecutor(e)
	case ast.BranchExpr:
		return branchExprExecutor(e)
	case ast.ArrayExpr:
		return arrayExprExecutor(e)
	case ast.RegexExpr:
		// Noop
		return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
			//ctx = WithExecutorID(ctx, "regexExpr")
			return data, nil
		}, nil
	case ast.SortByExpr:
		return sortByExprExecutor(e)
	case ast.NullExpr:
		return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
			//ctx = WithExecutorID(ctx, "nullExpr")
			return model.NewNullValue(), nil
		}, nil
	default:
		return nil, fmt.Errorf("unhandled expression type: %T", e)
	}
}

func chainedExprExecutor(e ast.ChainedExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "chainedExpr")
		var curData = data
		for _, expr := range e.Exprs {
			res, err := ExecuteAST(ctx, expr, curData, options)
			if err != nil {
				return nil, fmt.Errorf("error executing expression: %w", err)
			}
			curData = res
		}
		return curData, nil
	}, nil
}

func variableExprExecutor(e ast.VariableExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		//ctx = WithExecutorID(ctx, "variableExpr")
		varName := e.Name
		res, ok := options.Vars[varName]
		if ok {
			return res, nil
		}

		envVarValue := os.Getenv(varName)
		if envVarValue != "" {
			return model.NewStringValue(envVarValue), nil
		}

		return nil, fmt.Errorf("variable %s not found", varName)
	}, nil
}


================================================
FILE: execution/execute_array.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func arrayExprExecutor(e ast.ArrayExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "arrayExpr")
		res := model.NewSliceValue()

		for _, expr := range e.Exprs {
			el, err := ExecuteAST(ctx, expr, data, options)
			if err != nil {
				return nil, err
			}

			if el.IsSpread() {
				if err := el.RangeSlice(func(_ int, value *model.Value) error {
					if err := res.Append(value); err != nil {
						return err
					}
					return nil
				}); err != nil {
					return nil, err
				}
			} else {
				if err := res.Append(el); err != nil {
					return nil, err
				}
			}
		}

		return res, nil
	}, nil
}

func rangeExprExecutor(e ast.RangeExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "rangeExpr")
		var start, end int64 = 0, -1
		if e.Start != nil {
			startE, err := ExecuteAST(ctx, e.Start, data, options)
			if err != nil {
				return nil, fmt.Errorf("error evaluating start expression: %w", err)
			}

			start, err = startE.IntValue()
			if err != nil {
				return nil, fmt.Errorf("error getting start int value: %w", err)
			}
		}

		if e.End != nil {
			endE, err := ExecuteAST(ctx, e.End, data, options)
			if err != nil {
				return nil, fmt.Errorf("error evaluating end expression: %w", err)
			}

			end, err = endE.IntValue()
			if err != nil {
				return nil, fmt.Errorf("error getting end int value: %w", err)
			}
		}

		var res *model.Value
		var err error

		switch data.Type() {
		case model.TypeString:
			res, err = data.StringIndexRange(int(start), int(end))
		case model.TypeSlice:
			res, err = data.SliceIndexRange(int(start), int(end))
		default:
			err = fmt.Errorf("range expects a slice or string, got %s", data.Type())
		}

		if err != nil {
			return nil, err
		}

		return res, nil
	}, nil
}

func indexExprExecutor(e ast.IndexExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "indexExpr")
		indexE, err := ExecuteAST(ctx, e.Index, data, options)
		if err != nil {
			return nil, fmt.Errorf("error evaluating index expression: %w", err)
		}

		index, err := indexE.IntValue()
		if err != nil {
			return nil, fmt.Errorf("error getting index int value: %w", err)
		}

		return data.GetSliceIndex(int(index))
	}, nil
}


================================================
FILE: execution/execute_array_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestArray(t *testing.T) {
	inSlice := func() *model.Value {
		s := model.NewSliceValue()
		if err := s.Append(model.NewIntValue(1)); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if err := s.Append(model.NewIntValue(2)); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if err := s.Append(model.NewIntValue(3)); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		return s
	}
	inMap := func() *model.Value {
		m := model.NewMapValue()
		if err := m.SetMapKey("numbers", inSlice()); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		return m
	}

	runArrayTests := func(in func() *model.Value, prefix string) func(t *testing.T) {
		return func(t *testing.T) {
			t.Run("1:2", testCase{
				s:    prefix + `[1:2]`,
				inFn: in,
				outFn: func() *model.Value {
					res := model.NewSliceValue()
					if err := res.Append(model.NewIntValue(2)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					if err := res.Append(model.NewIntValue(3)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					return res
				},
			}.run)
			t.Run("1:0", testCase{
				s:    prefix + `[1:0]`,
				inFn: in,
				outFn: func() *model.Value {
					res := model.NewSliceValue()
					if err := res.Append(model.NewIntValue(2)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					if err := res.Append(model.NewIntValue(1)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					return res
				},
			}.run)
			t.Run("1:", testCase{
				s:    prefix + `[1:]`,
				inFn: in,
				outFn: func() *model.Value {
					res := model.NewSliceValue()
					if err := res.Append(model.NewIntValue(2)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					if err := res.Append(model.NewIntValue(3)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					return res
				},
			}.run)
			t.Run(":1", testCase{
				s:    prefix + `[:1]`,
				inFn: in,
				outFn: func() *model.Value {
					res := model.NewSliceValue()
					if err := res.Append(model.NewIntValue(1)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					if err := res.Append(model.NewIntValue(2)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					return res
				},
			}.run)
			t.Run("reverse", testCase{
				s:    prefix + `[len($this)-1:0]`,
				inFn: in,
				outFn: func() *model.Value {
					res := model.NewSliceValue()
					if err := res.Append(model.NewIntValue(3)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					if err := res.Append(model.NewIntValue(2)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					if err := res.Append(model.NewIntValue(1)); err != nil {
						t.Fatalf("unexpected error: %s", err)
					}
					return res
				},
			}.run)
		}
	}

	t.Run("direct to slice", runArrayTests(inSlice, "$this"))
	t.Run("property to slice", runArrayTests(inMap, "numbers"))
}


================================================
FILE: execution/execute_assign.go
================================================
package execution

import (
	"context"
	"fmt"
	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func executeAssign(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
	err := left.Set(right)
	if err != nil {
		return nil, fmt.Errorf("error setting value: %w", err)
	}
	return right, nil
}


================================================
FILE: execution/execute_assign_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/execution"
	"github.com/tomwright/dasel/v3/model"
)

func TestAssignVariable(t *testing.T) {
	t.Run("single assign", testCase{
		s: `$x=1`,
		outFn: func() *model.Value {
			r := model.NewIntValue(1)
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("double assign", testCase{
		s: `$x=1;$y=$x+1`,
		outFn: func() *model.Value {
			r := model.NewIntValue(2)
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("multiple assign with final statement", testCase{
		s: `$first = 'Tom';
$last = 'Wright';
$full = $first + ' ' + $last;
{first: $first, last: $last, full: $full}`,
		outFn: func() *model.Value {
			r := model.NewMapValue()
			if err := r.SetMapKey("first", model.NewStringValue("Tom")); err != nil {
				t.Fatalf("Unexpected error: %s", err)
			}
			if err := r.SetMapKey("last", model.NewStringValue("Wright")); err != nil {
				t.Fatalf("Unexpected error: %s", err)
			}
			if err := r.SetMapKey("full", model.NewStringValue("Tom Wright")); err != nil {
				t.Fatalf("Unexpected error: %s", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("multiple assign with final statement and mixed case variables", testCase{
		s: `$firstName = 'Tom';
$lastName = 'Wright';
$fullName = $firstName + ' ' + $lastName;
{firstName: $firstName, lastName: $lastName, fullName: $fullName}`,
		outFn: func() *model.Value {
			r := model.NewMapValue()
			if err := r.SetMapKey("firstName", model.NewStringValue("Tom")); err != nil {
				t.Fatalf("Unexpected error: %s", err)
			}
			if err := r.SetMapKey("lastName", model.NewStringValue("Wright")); err != nil {
				t.Fatalf("Unexpected error: %s", err)
			}
			if err := r.SetMapKey("fullName", model.NewStringValue("Tom Wright")); err != nil {
				t.Fatalf("Unexpected error: %s", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("self referencing variable", testCase{
		s: `$x=1;$x=$x*2`,
		outFn: func() *model.Value {
			r := model.NewIntValue(2)
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
}


================================================
FILE: execution/execute_binary.go
================================================
package execution

import (
	"context"
	"errors"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
	"github.com/tomwright/dasel/v3/selector/lexer"
)

type binaryExpressionExecutorFn func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error)

func basicBinaryExpressionExecutorFn(handler func(ctx context.Context, left *model.Value, right *model.Value, e ast.BinaryExpr) (*model.Value, error)) binaryExpressionExecutorFn {
	return func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error) {
		left, err := ExecuteAST(ctx, expr.Left, value, options)
		if err != nil {
			return nil, fmt.Errorf("error evaluating left expression: %w", err)
		}

		if !left.IsBranch() {
			right, err := ExecuteAST(ctx, expr.Right, value, options)
			if err != nil {
				return nil, fmt.Errorf("error evaluating right expression: %w", err)
			}
			res, err := handler(ctx, left, right, expr)
			if err != nil {
				return nil, err
			}
			return res, nil
		}

		res := model.NewSliceValue()
		res.MarkAsBranch()
		if err := left.RangeSlice(func(i int, v *model.Value) error {
			right, err := ExecuteAST(ctx, expr.Right, v, options)
			if err != nil {
				return fmt.Errorf("error evaluating right expression: %w", err)
			}
			r, err := handler(ctx, v, right, expr)
			if err != nil {
				return err
			}
			if err := res.Append(r); err != nil {
				return err
			}
			return nil
		}); err != nil {
			return nil, err
		}
		return res, nil
	}
}

var binaryExpressionExecutors = map[lexer.TokenKind]binaryExpressionExecutorFn{}

func binaryExprExecutor(e ast.BinaryExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "binaryExpr")
		if e.Left == nil || e.Right == nil {
			return nil, fmt.Errorf("left and right expressions must be provided")
		}

		exec, ok := binaryExpressionExecutors[e.Operator.Kind]
		if !ok {
			return nil, fmt.Errorf("unhandled operator: %s", e.Operator.Value)
		}

		return exec(ctx, e, data, options)
	}, nil
}

func init() {
	binaryExpressionExecutors[lexer.Plus] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.Add(right)
	})
	binaryExpressionExecutors[lexer.Dash] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.Subtract(right)
	})
	binaryExpressionExecutors[lexer.Star] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.Multiply(right)
	})
	binaryExpressionExecutors[lexer.Slash] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.Divide(right)
	})
	binaryExpressionExecutors[lexer.Percent] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.Modulo(right)
	})
	binaryExpressionExecutors[lexer.GreaterThan] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.GreaterThan(right)
	})
	binaryExpressionExecutors[lexer.GreaterThanOrEqual] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.GreaterThanOrEqual(right)
	})
	binaryExpressionExecutors[lexer.LessThan] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.LessThan(right)
	})
	binaryExpressionExecutors[lexer.LessThanOrEqual] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.LessThanOrEqual(right)
	})
	binaryExpressionExecutors[lexer.Equal] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.Equal(right)
	})
	binaryExpressionExecutors[lexer.NotEqual] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		return left.NotEqual(right)
	})
	binaryExpressionExecutors[lexer.Equals] = func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error) {
		if leftVar, ok := expr.Left.(ast.VariableExpr); ok {
			// It is expected that the left side of an assignment may not exist yet.
			if _, ok := options.Vars[leftVar.Name]; !ok {
				options.Vars[leftVar.Name] = model.NewNullValue()
			}
		}
		return basicBinaryExpressionExecutorFn(executeAssign)(ctx, expr, value, options)
	}
	binaryExpressionExecutors[lexer.And] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		leftBool, err := left.BoolValue()
		if err != nil {
			return nil, fmt.Errorf("error getting left bool value: %w", err)
		}
		rightBool, err := right.BoolValue()
		if err != nil {
			return nil, fmt.Errorf("error getting right bool value: %w", err)
		}
		return model.NewBoolValue(leftBool && rightBool), nil
	})
	binaryExpressionExecutors[lexer.Or] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, right *model.Value, _ ast.BinaryExpr) (*model.Value, error) {
		leftBool, err := left.BoolValue()
		if err != nil {
			return nil, fmt.Errorf("error getting left bool value: %w", err)
		}
		rightBool, err := right.BoolValue()
		if err != nil {
			return nil, fmt.Errorf("error getting right bool value: %w", err)
		}
		return model.NewBoolValue(leftBool || rightBool), nil
	})
	binaryExpressionExecutors[lexer.Like] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, _ *model.Value, e ast.BinaryExpr) (*model.Value, error) {
		leftStr, err := left.StringValue()
		if err != nil {
			return nil, fmt.Errorf("like requires left side to be a string, got %s", left.Type().String())
		}
		rightPatt, ok := e.Right.(ast.RegexExpr)
		if !ok {
			return nil, fmt.Errorf("like requires right side to be a regex pattern")
		}
		res := rightPatt.Regex.MatchString(leftStr)
		return model.NewBoolValue(res), nil
	})
	binaryExpressionExecutors[lexer.NotLike] = basicBinaryExpressionExecutorFn(func(ctx context.Context, left *model.Value, _ *model.Value, e ast.BinaryExpr) (*model.Value, error) {
		leftStr, err := left.StringValue()
		if err != nil {
			return nil, fmt.Errorf("like requires left side to be a string, got %s", left.Type().String())
		}
		rightPatt, ok := e.Right.(ast.RegexExpr)
		if !ok {
			return nil, fmt.Errorf("like requires right side to be a regex pattern")
		}
		res := rightPatt.Regex.MatchString(leftStr)
		return model.NewBoolValue(!res), nil
	})
	binaryExpressionExecutors[lexer.DoubleQuestionMark] = func(ctx context.Context, expr ast.BinaryExpr, value *model.Value, options *Options) (*model.Value, error) {
		left, err := ExecuteAST(ctx, expr.Left, value, options)

		if err == nil && !left.IsNull() {
			return left, nil
		}

		if err != nil {
			handleErrs := []any{
				model.ErrIncompatibleTypes{},
				model.ErrUnexpectedType{},
				model.ErrUnexpectedTypes{},
				model.SliceIndexOutOfRange{},
				model.MapKeyNotFound{},
			}
			for _, e := range handleErrs {
				if errors.As(err, &e) {
					err = nil
					break
				}
			}

			if err != nil {
				return nil, fmt.Errorf("error evaluating left expression: %w", err)
			}
		}

		// Do we need to handle branches here?
		right, err := ExecuteAST(ctx, expr.Right, value, options)
		if err != nil {
			return nil, fmt.Errorf("error evaluating right expression: %w", err)
		}
		return right, nil
	}
}


================================================
FILE: execution/execute_binary_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/model/orderedmap"
)

func TestBinary(t *testing.T) {
	t.Run("math", func(t *testing.T) {
		t.Run("literals", func(t *testing.T) {
			t.Run("addition", testCase{
				s:   `1 + 2`,
				out: model.NewIntValue(3),
			}.run)
			t.Run("subtraction", testCase{
				s:   `5 - 2`,
				out: model.NewIntValue(3),
			}.run)
			t.Run("multiplication", testCase{
				s:   `5 * 2`,
				out: model.NewIntValue(10),
			}.run)
			t.Run("division", testCase{
				s:   `10 / 2`,
				out: model.NewIntValue(5),
			}.run)
			t.Run("modulus", testCase{
				s:   `10 % 3`,
				out: model.NewIntValue(1),
			}.run)
			t.Run("ordering", testCase{
				s:   `45.2 + 5 * 4 - 2 / 2`, // 45.2 + (5 * 4) - (2 / 2) = 45.2 + 20 - 1 = 64.2
				out: model.NewFloatValue(64.2),
			}.run)
			t.Run("ordering with groups", testCase{
				s:   `(45.2 + 5) * ((4 - 2) / 2)`, // (45.2 + 5) * ((4 - 2) / 2) = (50.2) * ((2) / 2) = (50.2) * (1) = 50.2
				out: model.NewFloatValue(50.2),
			}.run)
			t.Run("ordering with groups", testCase{
				s:   `1 + 1 - 1 + 1 * 2`, // 1 + 1 - 1 + (1 * 2) = 1 + 1 - 1 + 2 = 3
				out: model.NewIntValue(3),
			}.run)
		})
		t.Run("variables", func(t *testing.T) {
			in := func() *model.Value {
				return model.NewValue(orderedmap.NewMap().
					Set("one", 1).
					Set("two", 2).
					Set("three", 3).
					Set("four", 4).
					Set("five", 5).
					Set("six", 6).
					Set("seven", 7).
					Set("eight", 8).
					Set("nine", 9).
					Set("ten", 10).
					Set("fortyfivepoint2", 45.2))
			}
			t.Run("addition", testCase{
				inFn: in,
				s:    `one + two`,
				out:  model.NewIntValue(3),
			}.run)
			t.Run("subtraction", testCase{
				inFn: in,
				s:    `five - two`,
				out:  model.NewIntValue(3),
			}.run)
			t.Run("multiplication", testCase{
				inFn: in,
				s:    `five * two`,
				out:  model.NewIntValue(10),
			}.run)
			t.Run("division", testCase{
				inFn: in,
				s:    `ten / two`,
				out:  model.NewIntValue(5),
			}.run)
			t.Run("modulus", testCase{
				inFn: in,
				s:    `ten % three`,
				out:  model.NewIntValue(1),
			}.run)
			t.Run("ordering", testCase{
				inFn: in,
				s:    `fortyfivepoint2 + five * four - two / two`, // 45.2 + (5 * 4) - (2 / 2) = 45.2 + 20 - 1 = 64.2
				out:  model.NewFloatValue(64.2),
			}.run)
			t.Run("ordering with groups", testCase{
				inFn: in,
				s:    `(fortyfivepoint2 + five) * ((four - two) / two)`, // (45.2 + 5) * ((4 - 2) / 2) = (50.2) * ((2) / 2) = (50.2) * (1) = 50.2
				out:  model.NewFloatValue(50.2),
			}.run)
		})
	})
	t.Run("comparison", func(t *testing.T) {
		t.Run("literals", func(t *testing.T) {
			t.Run("equal", testCase{
				s:   `1 == 1`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("not equal", testCase{
				s:   `1 != 1`,
				out: model.NewBoolValue(false),
			}.run)
			t.Run("greater than", testCase{
				s:   `2 > 1`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("greater than or equal", testCase{
				s:   `2 >= 2`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("less than", testCase{
				s:   `1 < 2`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("less than or equal", testCase{
				s:   `2 <= 2`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("like", testCase{
				s:   `"hello world" =~ r/ello/`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("not like", testCase{
				s:   `"hello world" !~ r/helloworld/`,
				out: model.NewBoolValue(true),
			}.run)
		})

		t.Run("variables", func(t *testing.T) {
			in := func() *model.Value {
				return model.NewValue(orderedmap.NewMap().
					Set("one", 1).
					Set("two", 2).
					Set("nested", orderedmap.NewMap().
						Set("three", 3).
						Set("four", 4)))
			}
			t.Run("equal", testCase{
				inFn: in,
				s:    `one == one`,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("not equal", testCase{
				inFn: in,
				s:    `one != one`,
				out:  model.NewBoolValue(false),
			}.run)
			t.Run("greater than", testCase{
				inFn: in,
				s:    `two > one`,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("greater than or equal", testCase{
				inFn: in,
				s:    `two >= two`,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("less than", testCase{
				inFn: in,
				s:    `one < two`,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("less than or equal", testCase{
				inFn: in,
				s:    `two <= two`,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("nested with math more than", testCase{
				inFn: in,
				s:    `nested.three + nested.four * 0 > one * 1`,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("nested with grouped math more than", testCase{
				inFn: in,
				s:    `(nested.three + nested.four) * 0 > one * 1`,
				out:  model.NewBoolValue(false),
			}.run)
		})

		t.Run("coalesce", func(t *testing.T) {
			t.Run("literals", func(t *testing.T) {
				t.Run("coalesce", testCase{
					s:   `null ?? 1`,
					out: model.NewIntValue(1),
				}.run)
				t.Run("coalesce with null", testCase{
					s:   `null ?? null`,
					out: model.NewNullValue(),
				}.run)
				t.Run("coalesce with null and value", testCase{
					s:   `null ?? 2`,
					out: model.NewIntValue(2),
				}.run)
				t.Run("coalesce with value", testCase{
					s:   `1 ?? 2`,
					out: model.NewIntValue(1),
				}.run)
			})
			t.Run("variables", func(t *testing.T) {
				in := func() *model.Value {
					return model.NewValue(orderedmap.NewMap().
						Set("one", 1).
						Set("two", 2).
						Set("nested", orderedmap.NewMap().
							Set("one", 1).
							Set("two", 2).
							Set("three", 3).
							Set("four", 4)).
						Set("list", []any{1, 2, 3}))
				}
				t.Run("coalesce", testCase{
					inFn: in,
					s:    `nested.five ?? one`,
					out:  model.NewIntValue(1),
				}.run)
				t.Run("coalesce with null", testCase{
					inFn: in,
					s:    `nested.five ?? null`,
					out:  model.NewNullValue(),
				}.run)
				t.Run("coalesce with null and value", testCase{
					inFn: in,
					s:    `nested.five ?? 2`,
					out:  model.NewIntValue(2),
				}.run)
				t.Run("coalesce with value", testCase{
					inFn: in,
					s:    `nested.three ?? 2`,
					out:  model.NewIntValue(3),
				}.run)
				t.Run("coalesce with bad map key", testCase{
					inFn: in,
					s:    `nope ?? 2`,
					out:  model.NewIntValue(2),
				}.run)
				t.Run("coalesce with nested bad map key", testCase{
					inFn: in,
					s:    `nested.nope ?? 2`,
					out:  model.NewIntValue(2),
				}.run)
				t.Run("coalesce with list index", testCase{
					inFn: in,
					s:    `list[1] ?? 5`,
					out:  model.NewIntValue(2),
				}.run)
				t.Run("coalesce with list bad index", testCase{
					inFn: in,
					s:    `list[3] ?? 5`,
					out:  model.NewIntValue(5),
				}.run)
				t.Run("chained coalesce execute left to right", func(t *testing.T) {
					// These tests ensure the coalesces run in order.
					t.Run("no match", testCase{
						inFn: in,
						s:    `nested.five ?? nested.six ?? nested.seven ?? 10`,
						out:  model.NewIntValue(10),
					}.run)
					t.Run("first match when all exist", testCase{
						inFn: in,
						s:    `nested.one ?? nested.two ?? nested.three ?? 10`,
						out:  model.NewIntValue(1),
					}.run)
					t.Run("second match", testCase{
						inFn: in,
						s:    `nested.five ?? nested.two ?? nested.three ?? 10`,
						out:  model.NewIntValue(2),
					}.run)
					t.Run("third match", testCase{
						inFn: in,
						s:    `nested.five ?? nested.six ?? nested.three ?? 10`,
						out:  model.NewIntValue(3),
					}.run)
				})
			})
		})
	})
}


================================================
FILE: execution/execute_branch.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func branchExprExecutor(e ast.BranchExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "branchExpr")
		res := model.NewSliceValue()
		res.MarkAsBranch()

		if len(e.Exprs) == 0 {
			// No expressions given. We'll branch on the input data.
			if err := data.RangeSlice(func(_ int, value *model.Value) error {
				if err := res.Append(value); err != nil {
					return fmt.Errorf("failed to append branch result: %w", err)
				}
				return nil
			}); err != nil {
				return nil, fmt.Errorf("failed to range slice: %w", err)
			}
		} else {
			for _, expr := range e.Exprs {
				r, err := ExecuteAST(ctx, expr, data, options)
				if err != nil {
					return nil, fmt.Errorf("failed to execute branch expr: %w", err)
				}

				// This deals with the spread operator in the branch expression.
				valsToAppend, err := prepareSpreadValues(r)
				if err != nil {
					return nil, fmt.Errorf("error handling spread values: %w", err)
				}
				for _, v := range valsToAppend {
					if err := res.Append(v); err != nil {
						return nil, fmt.Errorf("failed to append branch result: %w", err)
					}
				}
			}
		}

		return res, nil
	}, nil
}


================================================
FILE: execution/execute_branch_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/execution"
	"github.com/tomwright/dasel/v3/model"
)

func TestBranch(t *testing.T) {
	t.Run("single branch", testCase{
		s: "branch(1)",
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			r.MarkAsBranch()
			if err := r.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("many branches", testCase{
		s: "branch(1, 1+1, 3/1, 123)",
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			r.MarkAsBranch()
			if err := r.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(123)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("spread into many branches", testCase{
		s: "[1,2,3].branch(...)",
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			r.MarkAsBranch()
			if err := r.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	//t.Run("chained branch set", testCase{
	//	s: "branch(1, 2, 3).=5",
	//	outFn: func() *model.Value {
	//		r := model.NewSliceValue()
	//		r.MarkAsBranch()
	//		if err := r.Append(model.NewIntValue(5)); err != nil {
	//			t.Fatalf("unexpected error: %v", err)
	//		}
	//		if err := r.Append(model.NewIntValue(5)); err != nil {
	//			t.Fatalf("unexpected error: %v", err)
	//		}
	//		if err := r.Append(model.NewIntValue(5)); err != nil {
	//			t.Fatalf("unexpected error: %v", err)
	//		}
	//		return r
	//	},
	//	opts: []execution.ExecuteOptionFn{
	//		execution.WithUnstable(),
	//	},
	//}.run)
	t.Run("chained branch math", testCase{
		s: "(branch(1, 2, 3)) * 2",
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			r.MarkAsBranch()
			if err := r.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(4)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(6)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("chained branch math using branched value", testCase{
		s: `branch({"x":1}, {"x":2}, {"x":3}).x * $this`,
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			r.MarkAsBranch()
			if err := r.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(4)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(9)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
	t.Run("map on branch", testCase{
		s: `branch([1], [2], [3]).map($this * 2).branch()`,
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			r.MarkAsBranch()
			if err := r.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(4)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewIntValue(6)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
		opts: []execution.ExecuteOptionFn{
			execution.WithUnstable(),
		},
	}.run)
}


================================================
FILE: execution/execute_conditional.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func conditionalExprExecutor(e ast.ConditionalExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "conditionalExpr")
		cond, err := ExecuteAST(ctx, e.Cond, data, options)
		if err != nil {
			return nil, fmt.Errorf("error evaluating condition: %w", err)
		}

		condBool, err := cond.BoolValue()
		if err != nil {
			return nil, fmt.Errorf("error converting condition to boolean: %w", err)
		}

		if condBool {
			res, err := ExecuteAST(ctx, e.Then, data, options)
			if err != nil {
				return nil, fmt.Errorf("error executing then block: %w", err)
			}
			return res, nil
		}

		if e.Else != nil {
			res, err := ExecuteAST(ctx, e.Else, data, options)
			if err != nil {
				return nil, fmt.Errorf("error executing else block: %w", err)
			}
			return res, nil
		}

		return model.NewNullValue(), nil
	}, nil
}


================================================
FILE: execution/execute_conditional_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestConditional(t *testing.T) {
	t.Run("true", testCase{
		s:   `if (true) { "yes" } else { "no" }`,
		out: model.NewStringValue("yes"),
	}.run)
	t.Run("false", testCase{
		s:   `if (false) { "yes" } else { "no" }`,
		out: model.NewStringValue("no"),
	}.run)
	t.Run("nested", testCase{
		s: `
				if (true) {
					if (true) { "yes" }
					else { "no" }
				} else { "no" }`,
		out: model.NewStringValue("yes"),
	}.run)
	t.Run("nested false", testCase{
		s: `
				if (true) {
					if (false) { "yes" }
					else { "no" }
				} else { "no" }`,
		out: model.NewStringValue("no"),
	}.run)
	t.Run("else if", testCase{
		s: `
				if (false) { "yes" }
				elseif (true) { "no" }
				else { "maybe" }`,
		out: model.NewStringValue("no"),
	}.run)
	t.Run("else if else", testCase{
		s: `
				if (false) { "yes" }
				elseif (false) { "no" }
				else { "maybe" }`,
		out: model.NewStringValue("maybe"),
	}.run)
	t.Run("if elseif elseif else", testCase{
		s: `
				if (false) { "yes" }
				elseif (false) { "no" }
				elseif (false) { "maybe" }
				else { "nope" }`,
		out: model.NewStringValue("nope"),
	}.run)
}


================================================
FILE: execution/execute_each.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func eachExprExecutor(e ast.EachExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "eachExpr")
		if !data.IsSlice() {
			return nil, fmt.Errorf("cannot each over non-array")
		}

		if err := data.RangeSlice(func(i int, item *model.Value) error {
			_, err := ExecuteAST(ctx, e.Expr, item, options)
			if err != nil {
				return err
			}
			return nil
		}); err != nil {
			return nil, fmt.Errorf("error ranging over slice: %w", err)
		}

		return data, nil
	}, nil
}


================================================
FILE: execution/execute_each_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestEach(t *testing.T) {
	t.Run("all true", testCase{
		s: "[1,2,3].each($this = $this + 1)",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			if err := s.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(4)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			return s
		},
	}.run)
}


================================================
FILE: execution/execute_filter.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func filterExprExecutor(e ast.FilterExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "filterExpr")
		if !data.IsSlice() {
			return nil, fmt.Errorf("cannot filter over non-array")
		}
		res := model.NewSliceValue()

		if err := data.RangeSlice(func(i int, item *model.Value) error {
			v, err := ExecuteAST(ctx, e.Expr, item, options)
			if err != nil {
				return err
			}

			boolV, err := v.BoolValue()
			if err != nil {
				return err
			}

			if !boolV {
				return nil
			}
			if err := res.Append(item); err != nil {
				return fmt.Errorf("error appending item to result: %w", err)
			}
			return nil
		}); err != nil {
			return nil, fmt.Errorf("error ranging over slice: %w", err)
		}

		return res, nil
	}, nil
}


================================================
FILE: execution/execute_filter_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFilter(t *testing.T) {
	inSlice := func() *model.Value {
		s := model.NewSliceValue()
		if err := s.Append(model.NewIntValue(1)); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if err := s.Append(model.NewIntValue(2)); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		if err := s.Append(model.NewIntValue(3)); err != nil {
			t.Fatalf("unexpected error: %s", err)
		}
		return s
	}
	t.Run("all true", testCase{
		inFn: inSlice,
		s:    "filter(true)",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			if err := s.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			return s
		},
	}.run)
	t.Run("all !false", testCase{
		inFn: inSlice,
		s:    "filter(!false)",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			if err := s.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			return s
		},
	}.run)
	t.Run("all false", testCase{
		inFn: inSlice,
		s:    "filter(false)",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			return s
		},
	}.run)
	t.Run("all !true", testCase{
		inFn: inSlice,
		s:    "filter(!true)",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			return s
		},
	}.run)
	t.Run("equal 2", testCase{
		inFn: inSlice,
		s:    "filter($this == 2)",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			if err := s.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			return s
		},
	}.run)
	t.Run("not equal 2", testCase{
		inFn: inSlice,
		s:    "filter($this != 2)",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			if err := s.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			return s
		},
	}.run)
}


================================================
FILE: execution/execute_func.go
================================================
package execution

import (
	"context"
	"errors"
	"fmt"
	"slices"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func prepareArgs(ctx context.Context, opts *Options, data *model.Value, argsE ast.Expressions) (model.Values, error) {
	args := make(model.Values, 0)
	for i, arg := range argsE {
		res, err := ExecuteAST(ctx, arg, data, opts)
		if err != nil {
			return nil, fmt.Errorf("error evaluating argument %d: %w", i, err)
		}

		argVals, err := prepareSpreadValues(res)
		if err != nil {
			return nil, fmt.Errorf("error handling spread values: %w", err)
		}

		args = append(args, argVals...)
	}
	return args, nil
}

func callFnExecutor(f FuncFn, argsE ast.Expressions) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "callFnExpr")
		args, err := prepareArgs(ctx, options, data, argsE)
		if err != nil {
			return nil, fmt.Errorf("error preparing arguments: %w", err)
		}

		res, err := f(ctx, data, args)
		if err != nil {
			return nil, fmt.Errorf("error executing function: %w", err)
		}

		return res, nil
	}, nil
}

var unstableFuncs = []string{
	"ignore",
}

func callExprExecutor(options *Options, e ast.CallExpr) (expressionExecutor, error) {
	if !options.Unstable && (slices.Contains(unstableFuncs, e.Function)) {
		return nil, errors.New("unstable function are not enabled. to enable them use --unstable")
	}
	if f, ok := options.Funcs.Get(e.Function); ok {
		res, err := callFnExecutor(f, e.Args)
		if err != nil {
			return nil, fmt.Errorf("error executing function %q: %w", e.Function, err)
		}
		return res, nil
	}

	return nil, fmt.Errorf("unknown function: %q", e.Function)
}


================================================
FILE: execution/execute_func_test.go
================================================
package execution_test

import (
	"context"
	"testing"

	"github.com/tomwright/dasel/v3/execution"
	"github.com/tomwright/dasel/v3/model"
)

func TestFunc(t *testing.T) {
	returnInputData := execution.NewFunc(
		"returnInputData",
		func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
			return data, nil
		},
		execution.ValidateArgsExactly(0),
	)

	returnFirstArg := execution.NewFunc(
		"returnFirstArg",
		func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
			return args[0], nil
		},
		execution.ValidateArgsExactly(1),
	)

	funcs := execution.NewFuncCollection(
		returnInputData,
		returnFirstArg,
	)

	opts := []execution.ExecuteOptionFn{
		func(options *execution.Options) {
			options.Funcs = funcs
		},
	}

	t.Run("returnInputData", testCase{
		s:    `1.returnInputData()`,
		out:  model.NewIntValue(1),
		opts: opts,
	}.run)

	t.Run("returnFirstArg", testCase{
		s:    `1.returnFirstArg(2)`,
		out:  model.NewIntValue(2),
		opts: opts,
	}.run)
}


================================================
FILE: execution/execute_literal.go
================================================
package execution

import (
	"context"
	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func numberIntExprExecutor(e ast.NumberIntExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		//ctx = WithExecutorID(ctx, "numberIntExpr")
		return model.NewIntValue(e.Value), nil
	}, nil
}

func numberFloatExprExecutor(e ast.NumberFloatExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		//ctx = WithExecutorID(ctx, "numberFloatExpr")
		return model.NewFloatValue(e.Value), nil
	}, nil
}

func stringExprExecutor(e ast.StringExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		//ctx = WithExecutorID(ctx, "stringExpr")
		return model.NewStringValue(e.Value), nil
	}, nil
}

func boolExprExecutor(e ast.BoolExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		//ctx = WithExecutorID(ctx, "boolExpr")
		return model.NewBoolValue(e.Value), nil
	}, nil
}


================================================
FILE: execution/execute_literal_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestLiteral(t *testing.T) {
	t.Run("string", testCase{
		s:   `"hello"`,
		out: model.NewStringValue("hello"),
	}.run)
	t.Run("int", testCase{
		s:   `123`,
		out: model.NewIntValue(123),
	}.run)
	t.Run("float", testCase{
		s:   `123.4`,
		out: model.NewFloatValue(123.4),
	}.run)
	t.Run("true", testCase{
		s:   `true`,
		out: model.NewBoolValue(true),
	}.run)
	t.Run("false", testCase{
		s:   `false`,
		out: model.NewBoolValue(false),
	}.run)
	t.Run("empty array", testCase{
		s: `[]`,
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			return r
		},
	}.run)
	t.Run("array with one element", testCase{
		s: `[1]`,
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			if err := r.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
	}.run)
	t.Run("array with many elements", testCase{
		s: `[1, 2.2, "foo", true, [1, 2, 3]]`,
		outFn: func() *model.Value {
			nested := model.NewSliceValue()
			if err := nested.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := nested.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := nested.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			r := model.NewSliceValue()
			if err := r.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewFloatValue(2.2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewStringValue("foo")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewBoolValue(true)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(nested); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
	}.run)
	t.Run("array with expressions", testCase{
		s: `[1 + 1, 2f - 2, "foo" + "bar", true || false, [1 + 1, 2 * 2, 3 / 3]]`,
		outFn: func() *model.Value {
			nested := model.NewSliceValue()
			if err := nested.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := nested.Append(model.NewIntValue(4)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := nested.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			r := model.NewSliceValue()
			if err := r.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewFloatValue(0)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewStringValue("foobar")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(model.NewBoolValue(true)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := r.Append(nested); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return r
		},
	}.run)
}


================================================
FILE: execution/execute_map.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func mapExprExecutor(e ast.MapExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "mapExpr")
		if !data.IsSlice() {
			return nil, fmt.Errorf("cannot map over non-array")
		}
		res := model.NewSliceValue()

		if err := data.RangeSlice(func(i int, item *model.Value) error {
			item, err := ExecuteAST(ctx, e.Expr, item, options)
			if err != nil {
				return err
			}
			if err := res.Append(item); err != nil {
				return fmt.Errorf("error appending item to result: %w", err)
			}
			return nil
		}); err != nil {
			return nil, fmt.Errorf("error ranging over slice: %w", err)
		}
		return res, nil
	}, nil
}


================================================
FILE: execution/execute_map_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/model/orderedmap"
)

func TestMap(t *testing.T) {
	t.Run("property from slice of maps", testCase{
		inFn: func() *model.Value {
			return model.NewValue([]any{
				orderedmap.NewMap().Set("number", 1),
				orderedmap.NewMap().Set("number", 2),
				orderedmap.NewMap().Set("number", 3),
			})
		},
		s: `map(number)`,
		outFn: func() *model.Value {
			return model.NewValue([]any{1, 2, 3})
		},
	}.run)
	t.Run("with chain of selectors", testCase{
		inFn: func() *model.Value {
			return model.NewValue([]any{
				orderedmap.NewMap().Set("foo", 1).Set("bar", 4),
				orderedmap.NewMap().Set("foo", 2).Set("bar", 5),
				orderedmap.NewMap().Set("foo", 3).Set("bar", 6),
			})
		},
		s: `
				map (
					{
						total: add( foo, bar, 1 )
					}
				)
				.map ( total )`,
		outFn: func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewValue(6)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewValue(8)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewValue(10)); err != nil {
				t.Fatal(err)
			}
			return res
		},
	}.run)
}


================================================
FILE: execution/execute_object.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func objectExprExecutor(e ast.ObjectExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "objectExpr")
		obj := model.NewMapValue()
		for _, p := range e.Pairs {

			if ast.IsType[ast.SpreadExpr](p.Key) {
				var val *model.Value
				var err error
				if p.Value != nil {
					// We need to spread the resulting value.
					val, err = ExecuteAST(ctx, p.Value, data, options)
					if err != nil {
						return nil, fmt.Errorf("error evaluating spread values: %w", err)
					}
				} else {
					val = data
				}

				if err := val.RangeMap(func(key string, value *model.Value) error {
					if err := obj.SetMapKey(key, value); err != nil {
						return fmt.Errorf("error setting map key: %w", err)
					}
					return nil
				}); err != nil {
					return nil, fmt.Errorf("error spreading into object: %w", err)
				}
				continue
			}

			key, err := ExecuteAST(ctx, p.Key, data, options)
			if err != nil {
				return nil, fmt.Errorf("error evaluating key: %w", err)
			}
			if !key.IsString() {
				return nil, fmt.Errorf("expected key to resolve to string, got %s", key.Type())
			}

			val, err := ExecuteAST(ctx, p.Value, data, options)
			if err != nil {
				return nil, fmt.Errorf("error evaluating value: %w", err)
			}

			keyStr, err := key.StringValue()
			if err != nil {
				return nil, fmt.Errorf("error getting string value: %w", err)
			}
			if err := obj.SetMapKey(keyStr, val); err != nil {
				return nil, fmt.Errorf("error setting map key: %w", err)
			}
		}
		return obj, nil
	}, nil
}

func propertyExprExecutor(e ast.PropertyExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "propertyExpr")
		key, err := ExecuteAST(ctx, e.Property, data, options)
		if err != nil {
			return nil, fmt.Errorf("error evaluating property: %w", err)
		}
		switch {
		case key.IsString():
			keyStr, err := key.StringValue()
			if err != nil {
				return nil, fmt.Errorf("error getting string value: %w", err)
			}

			return data.GetMapKey(keyStr)
		case key.IsInt():
			keyInt, err := key.IntValue()
			if err != nil {
				return nil, fmt.Errorf("error getting int value: %w", err)
			}
			return data.GetSliceIndex(int(keyInt))
		default:
			return nil, fmt.Errorf("expected key to be a string or int, got %s", key.Type())
		}
	}, nil
}


================================================
FILE: execution/execute_object_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/model/orderedmap"
)

func TestObject(t *testing.T) {
	inputMap := func() *model.Value {
		return model.NewValue(orderedmap.NewMap().
			Set("title", "Mr").
			Set("age", int64(30)).
			Set("name", orderedmap.NewMap().
				Set("first", "Tom").
				Set("last", "Wright")))
	}
	t.Run("get", testCase{
		in: inputMap(),
		s:  `{title}`,
		outFn: func() *model.Value {
			return model.NewValue(orderedmap.NewMap().Set("title", "Mr"))
		},
	}.run)
	t.Run("get multiple", testCase{
		in: inputMap(),
		s:  `{title, age}`,
		outFn: func() *model.Value {
			return model.NewValue(orderedmap.NewMap().Set("title", "Mr").Set("age", int64(30)))
		},
	}.run)
	t.Run("get with spread", testCase{
		in: inputMap(),
		s:  `{...}`,
		outFn: func() *model.Value {
			res := inputMap()
			return res
		},
	}.run)
	t.Run("set", testCase{
		in: inputMap(),
		s:  `{title:"Mrs"}`,
		outFn: func() *model.Value {
			res := model.NewMapValue()
			_ = res.SetMapKey("title", model.NewStringValue("Mrs"))
			return res
		},
	}.run)
	t.Run("set with spread", testCase{
		in: inputMap(),
		s:  `{..., title:"Mrs"}`,
		outFn: func() *model.Value {
			res := inputMap()
			_ = res.SetMapKey("title", model.NewStringValue("Mrs"))
			return res
		},
	}.run)
	t.Run("merge with spread", testCase{
		inFn: func() *model.Value {
			a := model.NewMapValue()
			if err := a.SetMapKey("foo", model.NewStringValue("afoo")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if err := a.SetMapKey("bar", model.NewStringValue("abar")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			b := model.NewMapValue()
			if err := b.SetMapKey("bar", model.NewStringValue("bbar")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if err := b.SetMapKey("baz", model.NewStringValue("bbaz")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			res := model.NewMapValue()
			if err := res.SetMapKey("a", a); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if err := res.SetMapKey("b", b); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			return res
		},
		s: `{a..., b..., x: 1}`,
		outFn: func() *model.Value {
			b := model.NewMapValue()
			if err := b.SetMapKey("foo", model.NewStringValue("afoo")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := b.SetMapKey("bar", model.NewStringValue("bbar")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := b.SetMapKey("baz", model.NewStringValue("bbaz")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := b.SetMapKey("x", model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return b
		},
	}.run)
}


================================================
FILE: execution/execute_recursive_descent.go
================================================
package execution

import (
	"context"
	"errors"
	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func recursiveDescentExprExecutor2(e ast.RecursiveDescentExpr) (expressionExecutor, error) {
	var doSearch func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error)
	findValue := func(ctx context.Context, options *Options, v *model.Value) (*model.Value, error) {
		property, err := ExecuteAST(ctx, e.Expr, v, options)
		if err != nil {
			handleErrs := []any{
				model.ErrIncompatibleTypes{},
				model.ErrUnexpectedType{},
				model.ErrUnexpectedTypes{},
				model.SliceIndexOutOfRange{},
				model.MapKeyNotFound{},
			}
			for _, e := range handleErrs {
				if errors.As(err, &e) {
					err = nil
					break
				}
			}
		}

		if err != nil {
			return nil, err
		}
		return property, nil
	}
	doSearch = func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error) {
		res := make([]*model.Value, 0)

		switch data.Type() {
		case model.TypeMap:
			if err := data.RangeMap(func(key string, v *model.Value) error {
				if v.IsScalar() {
					if e.IsWildcard {
						res = append(res, v)
					}
				} else {
					if !e.IsWildcard {
						property, err := findValue(ctx, options, v)
						if err != nil {
							return err
						}
						if property != nil {
							res = append(res, property)
						}
					}

					gotNext, err := doSearch(ctx, options, v)
					if err != nil {
						return err
					}
					res = append(res, gotNext...)
				}
				return nil
			}); err != nil {
				return nil, err
			}
		case model.TypeSlice:
			if err := data.RangeSlice(func(i int, v *model.Value) error {
				if v.IsScalar() {
					if e.IsWildcard {
						res = append(res, v)
					}
				} else {
					if !e.IsWildcard {
						property, err := findValue(ctx, options, v)
						if err != nil {
							return err
						}
						if property != nil {
							res = append(res, property)
						}
					}

					gotNext, err := doSearch(ctx, options, v)
					if err != nil {
						return err
					}
					res = append(res, gotNext...)
				}
				return nil
			}); err != nil {
				return nil, err
			}
		}

		return res, nil
	}

	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "recursiveDescentExpr")
		matches := model.NewSliceValue()

		found, err := doSearch(ctx, options, data)
		if err != nil {
			return nil, err
		}

		for _, f := range found {
			if err := matches.Append(f); err != nil {
				return nil, err
			}
		}

		return matches, nil
	}, nil
}


================================================
FILE: execution/execute_search.go
================================================
package execution

import (
	"context"
	"errors"
	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func searchExprExecutor(e ast.SearchExpr) (expressionExecutor, error) {
	var doSearch func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error)
	processValue := func(ctx context.Context, v *model.Value, options *Options) (bool, error) {
		got, err := ExecuteAST(ctx, e.Expr, v, options)
		if err != nil {
			handleErrs := []any{
				model.ErrIncompatibleTypes{},
				model.ErrUnexpectedType{},
				model.ErrUnexpectedTypes{},
				model.SliceIndexOutOfRange{},
				model.MapKeyNotFound{},
			}
			for _, e := range handleErrs {
				if errors.As(err, &e) {
					err = nil
					break
				}
			}
		}
		if err != nil {
			return false, err
		}

		if got == nil {
			return false, nil
		}

		gotV, err := got.BoolValue()
		if err != nil {
			return false, err
		}
		return gotV, nil
	}
	doSearch = func(ctx context.Context, options *Options, data *model.Value) ([]*model.Value, error) {
		res := make([]*model.Value, 0)

		switch data.Type() {
		case model.TypeMap:
			if err := data.RangeMap(func(key string, v *model.Value) error {
				match, err := processValue(ctx, v, options)
				if err != nil {
					return err
				}

				if match {
					res = append(res, v)
				}

				gotNext, err := doSearch(ctx, options, v)
				if err != nil {
					return err
				}
				res = append(res, gotNext...)

				return nil
			}); err != nil {
				return nil, err
			}
		case model.TypeSlice:
			if err := data.RangeSlice(func(i int, v *model.Value) error {
				match, err := processValue(ctx, v, options)
				if err != nil {
					return err
				}

				if match {
					res = append(res, v)
				}

				gotNext, err := doSearch(ctx, options, v)
				if err != nil {
					return err
				}
				res = append(res, gotNext...)

				return nil
			}); err != nil {
				return nil, err
			}
		}

		return res, nil
	}

	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "searchExpr")
		matches := model.NewSliceValue()

		found, err := doSearch(ctx, options, data)
		if err != nil {
			return nil, err
		}

		for _, f := range found {
			if err := matches.Append(f); err != nil {
				return nil, err
			}
		}

		return matches, nil
	}, nil
}


================================================
FILE: execution/execute_sort_by.go
================================================
package execution

import (
	"context"
	"fmt"
	"slices"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
)

func sortByExprExecutor(e ast.SortByExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "sortByExpr")
		if !data.IsSlice() {
			return nil, fmt.Errorf("cannot sort by on non-slice data")
		}

		type sortableValue struct {
			index int
			value *model.Value
		}
		values := make([]sortableValue, 0)

		if err := data.RangeSlice(func(i int, item *model.Value) error {
			item, err := ExecuteAST(ctx, e.Expr, item, options)
			if err != nil {
				return err
			}
			values = append(values, sortableValue{
				index: i,
				value: item,
			})
			return nil
		}); err != nil {
			return nil, fmt.Errorf("error ranging over slice: %w", err)
		}

		slices.SortFunc(values, func(i, j sortableValue) int {
			res, err := i.value.Compare(j.value)
			if err != nil {
				return 0
			}
			if e.Descending {
				return -res
			}
			return res
		})

		res := model.NewSliceValue()

		for _, i := range values {
			item, err := data.GetSliceIndex(i.index)
			if err != nil {
				return nil, fmt.Errorf("error getting slice index: %w", err)
			}
			if err := res.Append(item); err != nil {
				return nil, fmt.Errorf("error appending item to result: %w", err)
			}
		}

		return res, nil
	}, nil
}


================================================
FILE: execution/execute_sort_by_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncSortBy(t *testing.T) {
	runSortTests := func(in func() *model.Value, outAsc func() *model.Value, outDesc func() *model.Value) func(*testing.T) {
		return func(t *testing.T) {
			t.Run("asc default", testCase{
				inFn:  in,
				s:     `sortBy($this)`,
				outFn: outAsc,
			}.run)
			t.Run("asc", testCase{
				inFn:  in,
				s:     `sortBy($this, asc)`,
				outFn: outAsc,
			}.run)
			t.Run("desc", testCase{
				inFn:  in,
				s:     `sortBy($this, desc)`,
				outFn: outDesc,
			}.run)
		}
	}

	t.Run("int", runSortTests(
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewIntValue(2)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(1)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(4)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(3)); err != nil {
				t.Fatal(err)
			}
			return res
		},
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewIntValue(1)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(2)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(3)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(4)); err != nil {
				t.Fatal(err)
			}
			return res
		},
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewIntValue(4)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(3)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(2)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewIntValue(1)); err != nil {
				t.Fatal(err)
			}
			return res
		},
	))

	t.Run("float", runSortTests(
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewFloatValue(2.23)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(2)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(5.123)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(4.2)); err != nil {
				t.Fatal(err)
			}
			return res
		},
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewFloatValue(2)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(2.23)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(4.2)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(5.123)); err != nil {
				t.Fatal(err)
			}
			return res
		},
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewFloatValue(5.123)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(4.2)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(2.23)); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewFloatValue(2)); err != nil {
				t.Fatal(err)
			}
			return res
		},
	))
	t.Run("string", runSortTests(
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewStringValue("def")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("abc")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("cde")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("bcd")); err != nil {
				t.Fatal(err)
			}
			return res
		},
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewStringValue("abc")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("bcd")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("cde")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("def")); err != nil {
				t.Fatal(err)
			}
			return res
		},
		func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewStringValue("def")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("cde")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("bcd")); err != nil {
				t.Fatal(err)
			}
			if err := res.Append(model.NewStringValue("abc")); err != nil {
				t.Fatal(err)
			}
			return res
		},
	))
}


================================================
FILE: execution/execute_spread.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

func spreadExprExecutor() (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		//ctx = WithExecutorID(ctx, "spreadExpr")
		s := model.NewSliceValue()

		s.MarkAsSpread()

		switch {
		case data.IsSlice():
			if err := data.RangeSlice(func(key int, value *model.Value) error {
				if err := s.Append(value); err != nil {
					return fmt.Errorf("error appending value to slice: %w", err)
				}
				return nil
			}); err != nil {
				return nil, fmt.Errorf("error ranging slice: %w", err)
			}
		case data.IsMap():
			if err := data.RangeMap(func(key string, value *model.Value) error {
				if err := s.Append(value); err != nil {
					return fmt.Errorf("error appending value to slice: %w", err)
				}
				return nil
			}); err != nil {
				return nil, fmt.Errorf("error ranging map: %w", err)
			}
		default:
			return nil, fmt.Errorf("cannot spread on type %s", data.Type())
		}

		return s, nil
	}, nil
}

// prepareSpreadValues looks at the incoming value, and if we detect a spread value, we return the individual values.
func prepareSpreadValues(val *model.Value) (model.Values, error) {
	if val.IsSlice() && val.IsSpread() {
		sliceLen, err := val.SliceLen()
		if err != nil {
			return nil, fmt.Errorf("error getting slice length: %w", err)
		}
		values := make(model.Values, sliceLen)
		for i := 0; i < sliceLen; i++ {
			v, err := val.GetSliceIndex(i)
			if err != nil {
				return nil, fmt.Errorf("error getting slice index %d: %w", i, err)
			}
			values[i] = v
		}
		return values, nil
	}
	return model.Values{val}, nil
}


================================================
FILE: execution/execute_spread_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestSpread(t *testing.T) {
	t.Run("build new array", testCase{
		s: "[[1,2,3]..., 4]",
		outFn: func() *model.Value {
			s := model.NewSliceValue()
			if err := s.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			if err := s.Append(model.NewIntValue(4)); err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			return s
		},
	}.run)
}


================================================
FILE: execution/execute_test.go
================================================
package execution_test

import (
	"context"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/tomwright/dasel/v3/execution"
	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/model/orderedmap"
)

type testCase struct {
	in          *model.Value
	inFn        func() *model.Value
	s           string
	out         *model.Value
	outFn       func() *model.Value
	compareRoot bool
	opts        []execution.ExecuteOptionFn
}

func (tc testCase) run(t *testing.T) {
	in := tc.in
	if tc.inFn != nil {
		in = tc.inFn()
	}
	if in == nil {
		in = model.NewValue(nil)
	}
	exp := tc.out
	if tc.outFn != nil {
		exp = tc.outFn()
	}
	res, err := execution.ExecuteSelector(context.Background(), tc.s, in, execution.NewOptions(tc.opts...))
	if err != nil {
		t.Fatal(err)
	}

	if tc.compareRoot {
		res = in
	}

	equal, err := res.EqualTypeValue(exp)
	if err != nil {
		t.Fatal(err)
	}
	if !equal {
		t.Errorf("unexpected output:\nexp: %s\ngot: %s", exp.String(), res.String())
	}

	expMeta := exp.Metadata
	gotMeta := res.Metadata
	if !cmp.Equal(expMeta, gotMeta) {
		t.Errorf("unexpected output metadata: %v", cmp.Diff(expMeta, gotMeta))
	}
}

func TestExecuteSelector_HappyPath(t *testing.T) {
	t.Run("get", func(t *testing.T) {
		inputMap := func() *model.Value {
			return model.NewValue(
				orderedmap.NewMap().
					Set("title", "Mr").
					Set("age", int64(31)).
					Set("name", orderedmap.NewMap().
						Set("first", "Tom").
						Set("last", "Wright")),
			)
		}
		t.Run("property", testCase{
			in:  inputMap(),
			s:   `title`,
			out: model.NewStringValue("Mr"),
		}.run)
		t.Run("nested property", testCase{
			in:  inputMap(),
			s:   `name.first`,
			out: model.NewStringValue("Tom"),
		}.run)
		t.Run("concat with grouping", testCase{
			in:  inputMap(),
			s:   `title + " " + (name.first) + " " + (name.last)`,
			out: model.NewStringValue("Mr Tom Wright"),
		}.run)
		t.Run("concat", testCase{
			in:  inputMap(),
			s:   `title + " " + name.first + " " + name.last`,
			out: model.NewStringValue("Mr Tom Wright"),
		}.run)
		t.Run("add evaluated fields", testCase{
			in: inputMap(),
			s:  `{..., "over30": age > 30}`,
			outFn: func() *model.Value {
				return model.NewValue(
					orderedmap.NewMap().
						Set("title", "Mr").
						Set("age", int64(31)).
						Set("name", orderedmap.NewMap().
							Set("first", "Tom").
							Set("last", "Wright")).
						Set("over30", true),
				)
			},
		}.run)
	})

	t.Run("set", func(t *testing.T) {
		inputMap := func() *model.Value {
			return model.NewValue(
				orderedmap.NewMap().
					Set("title", "Mr").
					Set("age", int64(31)).
					Set("name", orderedmap.NewMap().
						Set("first", "Tom").
						Set("last", "Wright")),
			)
		}
		inputSlice := func() *model.Value {
			return model.NewValue([]any{1, 2, 3})
		}

		t.Run("set property", testCase{
			in: inputMap(),
			s:  `title = "Mrs"`,
			outFn: func() *model.Value {
				res := inputMap()
				if err := res.SetMapKey("title", model.NewStringValue("Mrs")); err != nil {
					t.Fatalf("unexpected error: %s", err)
				}
				return res
			},
			compareRoot: true,
		}.run)

		t.Run("set index", testCase{
			in: inputSlice(),
			s:  `$this[1] = 4`,
			outFn: func() *model.Value {
				res := inputSlice()
				if err := res.SetSliceIndex(1, model.NewIntValue(4)); err != nil {
					t.Fatalf("unexpected error: %s", err)
				}
				return res
			},
			compareRoot: true,
		}.run)
	})
}


================================================
FILE: execution/execute_unary.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/selector/ast"
	"github.com/tomwright/dasel/v3/selector/lexer"
)

func unaryExprExecutor(e ast.UnaryExpr) (expressionExecutor, error) {
	return func(ctx context.Context, options *Options, data *model.Value) (*model.Value, error) {
		ctx = WithExecutorID(ctx, "unaryExpr")
		right, err := ExecuteAST(ctx, e.Right, data, options)
		if err != nil {
			return nil, fmt.Errorf("error evaluating right expression: %w", err)
		}

		switch e.Operator.Kind {
		case lexer.Exclamation:
			boolV, err := right.BoolValue()
			if err != nil {
				return nil, fmt.Errorf("error converting value to boolean: %w", err)
			}
			return model.NewBoolValue(!boolV), nil
		default:
			return nil, fmt.Errorf("unhandled unary operator: %s", e.Operator.Value)
		}
	}, nil
}


================================================
FILE: execution/execute_unary_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/model/orderedmap"
)

func TestUnary(t *testing.T) {
	t.Run("not", func(t *testing.T) {
		t.Run("literals", func(t *testing.T) {
			t.Run("not true", testCase{
				s:   `!true`,
				out: model.NewBoolValue(false),
			}.run)
			t.Run("not not true", testCase{
				s:   `!!true`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("not not not true", testCase{
				s:   `!!!true`,
				out: model.NewBoolValue(false),
			}.run)
			t.Run("not false", testCase{
				s:   `!false`,
				out: model.NewBoolValue(true),
			}.run)
			t.Run("not not false", testCase{
				s:   `!!false`,
				out: model.NewBoolValue(false),
			}.run)
			t.Run("not not not false", testCase{
				s:   `!!!false`,
				out: model.NewBoolValue(true),
			}.run)
		})
		t.Run("variables", func(t *testing.T) {
			in := func() *model.Value {
				return model.NewValue(orderedmap.NewMap().
					Set("t", true).
					Set("f", false))
			}
			t.Run("not true", testCase{
				s:    `!t`,
				inFn: in,
				out:  model.NewBoolValue(false),
			}.run)
			t.Run("not not true", testCase{
				s:    `!!t`,
				inFn: in,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("not not not true", testCase{
				s:    `!!!t`,
				inFn: in,
				out:  model.NewBoolValue(false),
			}.run)
			t.Run("not false", testCase{
				s:    `!f`,
				inFn: in,
				out:  model.NewBoolValue(true),
			}.run)
			t.Run("not not false", testCase{
				s:    `!!f`,
				inFn: in,
				out:  model.NewBoolValue(false),
			}.run)
			t.Run("not not not false", testCase{
				s:    `!!!f`,
				inFn: in,
				out:  model.NewBoolValue(true),
			}.run)
		})
	})
}


================================================
FILE: execution/func.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

var (
	// DefaultFuncCollection is the default collection of functions that can be executed.
	DefaultFuncCollection = NewFuncCollection(
		FuncLen,
		FuncAdd,
		FuncToString,
		FuncToInt,
		FuncToFloat,
		FuncMerge,
		FuncReverse,
		FuncTypeOf,
		FuncMax,
		FuncMin,
		FuncIgnore,
		FuncBase64Encode,
		FuncBase64Decode,
		FuncParse,
		FuncReadFile,
		FuncHas,
		FuncGet,
		FuncContains,
		FuncSum,
		FuncJoin,
		FuncReplace,
		FuncKeys,
	)
)

// ArgsValidator is a function that validates the arguments passed to a function.
type ArgsValidator func(ctx context.Context, name string, args model.Values) error

// ValidateArgsExactly returns an ArgsValidator that validates that the number of arguments passed to a function is exactly the expected number.
func ValidateArgsExactly(expected int) ArgsValidator {
	return func(ctx context.Context, name string, args model.Values) error {
		if len(args) == expected {
			return nil
		}
		return fmt.Errorf("func %q expects exactly %d arguments, got %d", name, expected, len(args))
	}
}

// ValidateArgsMin returns an ArgsValidator that validates that the number of arguments passed to a function is at least the expected number.
func ValidateArgsMin(expected int) ArgsValidator {
	return func(ctx context.Context, name string, args model.Values) error {
		if len(args) >= expected {
			return nil
		}
		return fmt.Errorf("func %q expects at least %d arguments, got %d", name, expected, len(args))
	}
}

// ValidateArgsMax returns an ArgsValidator that validates that the number of arguments passed to a function is at most the expected number.
func ValidateArgsMax(expected int) ArgsValidator {
	return func(ctx context.Context, name string, args model.Values) error {
		if len(args) <= expected {
			return nil
		}
		return fmt.Errorf("func %q expects no more than %d arguments, got %d", name, expected, len(args))
	}
}

// ValidateArgsMinMax returns an ArgsValidator that validates that the number of arguments passed to a function is between the min and max expected numbers.
func ValidateArgsMinMax(min int, max int) ArgsValidator {
	return func(ctx context.Context, name string, args model.Values) error {
		if len(args) >= min && len(args) <= max {
			return nil
		}
		return fmt.Errorf("func %q expects between %d and %d arguments, got %d", name, min, max, len(args))
	}
}

// Func represents a function that can be executed.
type Func struct {
	name          string
	handler       FuncFn
	argsValidator ArgsValidator
}

// Handler returns a FuncFn that can be used to execute the function.
func (f *Func) Handler() FuncFn {
	return func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		if f.argsValidator != nil {
			if err := f.argsValidator(ctx, f.name, args); err != nil {
				return nil, err
			}
		}
		res, err := f.handler(ctx, data, args)
		if err != nil {
			return nil, fmt.Errorf("error execution func %q: %w", f.name, err)
		}
		return res, nil
	}
}

// NewFunc creates a new Func.
func NewFunc(name string, handler FuncFn, argsValidator ArgsValidator) *Func {
	return &Func{
		name:          name,
		handler:       handler,
		argsValidator: argsValidator,
	}
}

// FuncFn is a function that can be executed.
type FuncFn func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error)

// FuncCollection is a collection of functions that can be executed.
type FuncCollection map[string]FuncFn

// NewFuncCollection creates a new FuncCollection with the given functions.
func NewFuncCollection(funcs ...*Func) FuncCollection {
	return FuncCollection{}.Register(funcs...)
}

// Register registers the given functions with the FuncCollection.
func (fc FuncCollection) Register(funcs ...*Func) FuncCollection {
	for _, f := range funcs {
		fc[f.name] = f.Handler()
	}
	return fc
}

// Get returns the function with the given name.
func (fc FuncCollection) Get(name string) (FuncFn, bool) {
	fn, ok := fc[name]
	return fn, ok
}

// Delete deletes the functions with the given names.
func (fc FuncCollection) Delete(names ...string) FuncCollection {
	for _, name := range names {
		delete(fc, name)
	}
	return fc
}

// Copy returns a copy of the FuncCollection.
func (fc FuncCollection) Copy() FuncCollection {
	c := NewFuncCollection()
	for k, v := range fc {
		c[k] = v
	}
	return c
}


================================================
FILE: execution/func_add.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

// FuncAdd is a function that adds the given values together.
var FuncAdd = NewFunc(
	"add",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		var foundInts, foundFloats int
		var intRes int64
		var floatRes float64
		for _, arg := range args {
			if arg.IsFloat() {
				foundFloats++
				v, err := arg.FloatValue()
				if err != nil {
					return nil, fmt.Errorf("error getting float value: %w", err)
				}
				floatRes += v
				continue
			}
			if arg.IsInt() {
				foundInts++
				v, err := arg.IntValue()
				if err != nil {
					return nil, fmt.Errorf("error getting int value: %w", err)
				}
				intRes += v
				continue
			}
			return nil, fmt.Errorf("expected int or float, got %s", arg.Type())
		}
		if foundFloats > 0 {
			return model.NewFloatValue(floatRes + float64(intRes)), nil
		}
		return model.NewIntValue(intRes), nil
	},
	ValidateArgsMin(1),
)


================================================
FILE: execution/func_add_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/model/orderedmap"
)

func TestFuncAdd(t *testing.T) {
	t.Run("int", testCase{
		s:   `add(1, 2, 3)`,
		out: model.NewIntValue(6),
	}.run)
	t.Run("float", testCase{
		s:   `add(1f, 2.5, 3.5)`,
		out: model.NewFloatValue(7),
	}.run)
	t.Run("mixed", testCase{
		s:   `add(1, 2f)`,
		out: model.NewFloatValue(3),
	}.run)
	t.Run("properties", func(t *testing.T) {
		in := func() *model.Value {
			return model.NewValue(orderedmap.NewMap().
				Set("numbers", orderedmap.NewMap().
					Set("one", 1).
					Set("two", 2).
					Set("three", 3)).
				Set("nums", []any{1, 2, 3}))
		}
		t.Run("nested props", testCase{
			inFn: in,
			s:    `numbers.one + add(numbers.two, numbers.three)`,
			out:  model.NewIntValue(6),
		}.run)
		t.Run("add on end of chain", testCase{
			inFn: in,
			s:    `numbers.one + numbers.add(two, three)`,
			out:  model.NewIntValue(6),
		}.run)
		t.Run("add with map and spread on slice with $this addition and grouping", testCase{
			inFn: in,
			s:    `add(nums.map(($this + 1))...)`,
			out:  model.NewIntValue(9),
		}.run)
		t.Run("add with map and spread on slice with $this addition", testCase{
			inFn: in,
			s:    `add(nums.map($this + 1 - 2)...)`,
			out:  model.NewIntValue(3),
		}.run)
	})
}


================================================
FILE: execution/func_base64.go
================================================
package execution

import (
	"context"
	"encoding/base64"

	"github.com/tomwright/dasel/v3/model"
)

// FuncBase64Encode base64 encodes the given value.
var FuncBase64Encode = NewFunc(
	"base64e",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		arg := args[0]
		strVal, err := arg.StringValue()
		if err != nil {
			return nil, err
		}
		out := base64.StdEncoding.EncodeToString([]byte(strVal))
		return model.NewStringValue(out), nil
	},
	ValidateArgsExactly(1),
)

// FuncBase64Decode base64 decodes the given value.
var FuncBase64Decode = NewFunc(
	"base64d",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		arg := args[0]
		strVal, err := arg.StringValue()
		if err != nil {
			return nil, err
		}
		out, err := base64.StdEncoding.DecodeString(strVal)
		if err != nil {
			return nil, err
		}
		return model.NewStringValue(string(out)), nil
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_contains.go
================================================
package execution

import (
	"context"
	"fmt"
	"github.com/tomwright/dasel/v3/model"
)

// FuncContains is a function that returns the highest number.
var FuncContains = NewFunc(
	"contains",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		var contains bool

		target := args[0]

		length, err := data.SliceLen()
		if err != nil {
			return nil, fmt.Errorf("error getting slice length: %w", err)
		}

		for i := 0; i < length; i++ {
			v, err := data.GetSliceIndex(i)
			if err != nil {
				return nil, fmt.Errorf("error getting slice index %d: %w", i, err)
			}
			matches, err := v.Equal(target)
			if err != nil {
				continue
			}
			matchesBool, err := matches.BoolValue()
			if err != nil {
				return nil, err
			}
			if matchesBool {
				contains = true
				break
			}
		}

		return model.NewBoolValue(contains), nil
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_contains_test.go
================================================
package execution_test

import (
	"github.com/tomwright/dasel/v3/model"
	_ "github.com/tomwright/dasel/v3/parsing/json"
	"testing"
)

func TestFuncContains(t *testing.T) {
	t.Run("array true", testCase{
		s:   `[1,2,3,4,5].contains(3)`,
		out: model.NewBoolValue(true),
	}.run)
	t.Run("array false", testCase{
		s:   `[1,2,3,4,5].contains(6)`,
		out: model.NewBoolValue(false),
	}.run)
}


================================================
FILE: execution/func_get.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

// FuncGet is a function returns the value at the given key/index.
var FuncGet = NewFunc(
	"get",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {

		arg := args[0]

		switch arg.Type() {
		case model.TypeInt:
			index, err := arg.IntValue()
			if err != nil {
				return nil, err
			}
			return data.GetSliceIndex(int(index))
		case model.TypeString:
			key, err := arg.StringValue()
			if err != nil {
				return nil, err
			}
			return data.GetMapKey(key)
		default:
			return nil, fmt.Errorf("get expects string or int argument")
		}
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_get_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	_ "github.com/tomwright/dasel/v3/parsing/json"
)

func TestFuncGet(t *testing.T) {
	t.Run("returns array element", testCase{
		s:   `[1,2,3,4,5].get(3)`,
		out: model.NewIntValue(4),
	}.run)
	t.Run("returns map key", testCase{
		s:   `{"a": 3, "b": 4, "c": 5}.get("b")`,
		out: model.NewIntValue(4),
	}.run)
	t.Run("coalesce with invalid map accessor", testCase{
		s:   `{}.get("a") ?? "missing"`,
		out: model.NewStringValue("missing"),
	}.run)
	t.Run("returns null when string accessor used on slice", testCase{
		s:   `[].get(0) ?? "missing"`,
		out: model.NewStringValue("missing"),
	}.run)
}


================================================
FILE: execution/func_has.go
================================================
package execution

import (
	"context"
	"fmt"
	"github.com/tomwright/dasel/v3/model"
)

// FuncHas is a function that true or false if the input has the given key/index.
var FuncHas = NewFunc(
	"has",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {

		arg := args[0]

		switch arg.Type() {
		case model.TypeInt:
			// Given key is int, expect a slice.
			if data.Type() != model.TypeSlice {
				return model.NewBoolValue(false), nil
			}
			index, err := arg.IntValue()
			if err != nil {
				return nil, err
			}
			sliceLen, err := data.SliceLen()
			if err != nil {
				return nil, err
			}
			return model.NewBoolValue(index >= 0 && index < int64(sliceLen)), nil
		case model.TypeString:
			// Given key is string, expect a map.
			if data.Type() != model.TypeMap {
				return model.NewBoolValue(false), nil
			}
			key, err := arg.StringValue()
			if err != nil {
				return nil, err
			}
			exists, err := data.MapKeyExists(key)
			if err != nil {
				return nil, err
			}
			return model.NewBoolValue(exists), nil
		default:
			return nil, fmt.Errorf("has expects string or int argument")
		}
	},
	ValidateArgsMin(1),
)


================================================
FILE: execution/func_has_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncHas(t *testing.T) {
	t.Run("index in range", testCase{
		s:   `[1,2,3].has(0)`,
		out: model.NewBoolValue(true),
	}.run)
	t.Run("negative index", testCase{
		s:   `[1,2,3].has(-1)`,
		out: model.NewBoolValue(false),
	}.run)
	t.Run("index overflow", testCase{
		s:   `[1,2,3].has(3)`,
		out: model.NewBoolValue(false),
	}.run)
	t.Run("index string", testCase{
		s:   `[1,2,3].has("foo")`,
		out: model.NewBoolValue(false),
	}.run)
	t.Run("has map key", testCase{
		s:   `{"x":1}.has("x")`,
		out: model.NewBoolValue(true),
	}.run)
	t.Run("does not have map key", testCase{
		s:   `{"x":1}.has("y")`,
		out: model.NewBoolValue(false),
	}.run)
	t.Run("does not have map index", testCase{
		s:   `{"x":1}.has(1)`,
		out: model.NewBoolValue(false),
	}.run)
}


================================================
FILE: execution/func_ignore.go
================================================
package execution

import (
	"context"
	"github.com/tomwright/dasel/v3/model"
)

// FuncIgnore is a function that ignores the value, causing it to be rejected from a branch.
var FuncIgnore = NewFunc(
	"ignore",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		data.MarkAsIgnore()
		return data, nil
	},
	ValidateArgsExactly(0),
)


================================================
FILE: execution/func_join.go
================================================
package execution

import (
	"context"
	"fmt"
	"strings"

	"github.com/tomwright/dasel/v3/model"
)

// FuncJoin is a function that joins the given data or args to a string.
var FuncJoin = NewFunc(
	"join",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		separator, err := args[0].StringValue()
		if err != nil {
			return nil, fmt.Errorf("join expects a string separator as the first argument: %w", err)
		}

		var valuesToJoin []string

		if len(args) == 2 && args[1].IsSlice() {
			if err := args[1].RangeSlice(func(i int, value *model.Value) error {
				strVal, err := value.StringValue()
				if err != nil {
					return fmt.Errorf("could not read string value of index %d: %w", i, err)
				}
				valuesToJoin = append(valuesToJoin, strVal)
				return nil
			}); err != nil {
				return nil, err
			}
		} else if len(args) > 1 {
			// Join the args
			for i := 1; i < len(args); i++ {
				strVal, err := args[i].StringValue()
				if err != nil {
					return nil, fmt.Errorf("could not read string value of argument index %d: %w", i, err)
				}
				valuesToJoin = append(valuesToJoin, strVal)
			}
		} else {
			if err := data.RangeSlice(func(i int, value *model.Value) error {
				strVal, err := value.StringValue()
				if err != nil {
					return fmt.Errorf("could not read string value of index %d: %w", i, err)
				}
				valuesToJoin = append(valuesToJoin, strVal)
				return nil
			}); err != nil {
				return nil, err
			}
		}

		joined := strings.Join(valuesToJoin, separator)

		return model.NewStringValue(joined), nil
	},
	ValidateArgsMin(1),
)


================================================
FILE: execution/func_join_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	_ "github.com/tomwright/dasel/v3/parsing/json"
)

func TestFuncJoin(t *testing.T) {
	t.Run("chained input", testCase{
		s:   `["a","b","c"].join(",")`,
		out: model.NewStringValue("a,b,c"),
	}.run)
	t.Run("vararg input", testCase{
		s:   `join(",", "a", "b", "c")`,
		out: model.NewStringValue("a,b,c"),
	}.run)
	t.Run("array input", testCase{
		s:   `join(",", ["a", "b", "c"])`,
		out: model.NewStringValue("a,b,c"),
	}.run)
}


================================================
FILE: execution/func_keys.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

// FuncKeys returns the keys of a map or the indices of a slice.
var FuncKeys = NewFunc(
	"keys",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		switch data.Type() {
		case model.TypeMap:
			keys, err := data.MapKeys()
			if err != nil {
				return nil, err
			}
			res := model.NewSliceValue()
			for _, key := range keys {
				if err := res.Append(model.NewStringValue(key)); err != nil {
					return nil, err
				}
			}
			return res, nil
		case model.TypeSlice:
			len, err := data.SliceLen()
			if err != nil {
				return nil, err
			}
			res := model.NewSliceValue()
			for i := 0; i < len; i++ {
				if err := res.Append(model.NewIntValue(int64(i))); err != nil {
					return nil, err
				}
			}
			return res, nil
		default:
			return nil, fmt.Errorf("keys can only be used on maps and slices")
		}
	},
	ValidateArgsExactly(0),
)


================================================
FILE: execution/func_keys_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
	_ "github.com/tomwright/dasel/v3/parsing/json"
)

func TestFuncKeys(t *testing.T) {
	t.Run("returns slice indices", testCase{
		s: `[1,2,3,4,5].keys()`,
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			for i := 0; i < 5; i++ {
				if err := r.Append(model.NewIntValue(int64(i))); err != nil {
					panic(err)
				}
			}
			return r
		},
	}.run)
	t.Run("returns map keys", testCase{
		s: `{"a": 3, "b": 4, "c": 5}.keys()`,
		outFn: func() *model.Value {
			r := model.NewSliceValue()
			for _, key := range []string{"a", "b", "c"} {
				if err := r.Append(model.NewStringValue(key)); err != nil {
					panic(err)
				}
			}
			return r
		},
	}.run)
}


================================================
FILE: execution/func_len.go
================================================
package execution

import (
	"context"
	"github.com/tomwright/dasel/v3/model"
)

// FuncLen is a function that returns the length of the given value.
var FuncLen = NewFunc(
	"len",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		arg := args[0]

		l, err := arg.Len()
		if err != nil {
			return nil, err
		}

		return model.NewIntValue(int64(l)), nil
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_len_test.go
================================================
package execution_test

import (
	"github.com/tomwright/dasel/v3/model"
	_ "github.com/tomwright/dasel/v3/parsing/json"
	"testing"
)

func TestFuncLen(t *testing.T) {
	t.Run("array", testCase{
		s:   `len([1,2,3])`,
		out: model.NewIntValue(3),
	}.run)
	t.Run("object", testCase{
		s:   `len({"foo":1,"bar":2,"baz":3})`,
		out: model.NewIntValue(3),
	}.run)
	t.Run("string", testCase{
		s:   `len("hello")`,
		out: model.NewIntValue(5),
	}.run)
}


================================================
FILE: execution/func_max.go
================================================
package execution

import (
	"context"
	"github.com/tomwright/dasel/v3/model"
)

// FuncMax is a function that returns the highest number.
var FuncMax = NewFunc(
	"max",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		res := model.NewNullValue()
		for _, arg := range args {
			if res.IsNull() {
				res = arg
				continue
			}
			gt, err := arg.GreaterThan(res)
			if err != nil {
				return nil, err
			}
			gtBool, err := gt.BoolValue()
			if err != nil {
				return nil, err
			}
			if gtBool {
				res = arg
			}
		}
		return res, nil
	},
	ValidateArgsMin(1),
)


================================================
FILE: execution/func_max_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncMax(t *testing.T) {
	t.Run("int", testCase{
		s:   `max(1, 2, 3)`,
		out: model.NewIntValue(3),
	}.run)
	t.Run("float", testCase{
		s:   `max(1f, 2.5, 3.5)`,
		out: model.NewFloatValue(3.5),
	}.run)
	t.Run("mixed", testCase{
		s:   `max(1, 2f)`,
		out: model.NewFloatValue(2),
	}.run)
}


================================================
FILE: execution/func_merge.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

// FuncMerge is a function that merges two or more items together.
var FuncMerge = NewFunc(
	"merge",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		if len(args) == 1 {
			return args[0], nil
		}

		expectedType := args[0].Type()

		switch expectedType {
		case model.TypeMap:
			break
		default:
			return nil, fmt.Errorf("merge exects a map, found %s", expectedType)
		}

		// Validate types match
		for _, a := range args {
			if a.Type() != expectedType {
				return nil, fmt.Errorf("merge expects all arguments to be of the same type. expected %s, got %s", expectedType.String(), a.Type().String())
			}
		}

		base := model.NewMapValue()

		for i := 0; i < len(args); i++ {
			next := args[i]

			nextKVs, err := next.MapKeyValues()
			if err != nil {
				return nil, fmt.Errorf("merge failed to extract key values for arg %d: %w", i, err)
			}

			for _, kv := range nextKVs {
				if err := base.SetMapKey(kv.Key, kv.Value); err != nil {
					return nil, fmt.Errorf("merge failed to set map key %s: %w", kv.Key, err)
				}
			}
		}

		return base, nil
	},
	ValidateArgsMin(1),
)


================================================
FILE: execution/func_merge_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncMerge(t *testing.T) {
	t.Run("shallow", testCase{
		inFn: func() *model.Value {
			a := model.NewMapValue()
			if err := a.SetMapKey("foo", model.NewStringValue("afoo")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if err := a.SetMapKey("bar", model.NewStringValue("abar")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			b := model.NewMapValue()
			if err := b.SetMapKey("bar", model.NewStringValue("bbar")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if err := b.SetMapKey("baz", model.NewStringValue("bbaz")); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			res := model.NewMapValue()
			if err := res.SetMapKey("a", a); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			if err := res.SetMapKey("b", b); err != nil {
				t.Errorf("unexpected error: %v", err)
			}
			return res
		},
		s: `merge(a, b)`,
		outFn: func() *model.Value {
			b := model.NewMapValue()
			if err := b.SetMapKey("foo", model.NewStringValue("afoo")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := b.SetMapKey("bar", model.NewStringValue("bbar")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := b.SetMapKey("baz", model.NewStringValue("bbaz")); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return b
		},
	}.run)
}


================================================
FILE: execution/func_min.go
================================================
package execution

import (
	"context"
	"github.com/tomwright/dasel/v3/model"
)

// FuncMin is a function that returns the smalled number.
var FuncMin = NewFunc(
	"min",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		res := model.NewNullValue()
		for _, arg := range args {
			if res.IsNull() {
				res = arg
				continue
			}
			lt, err := arg.LessThan(res)
			if err != nil {
				return nil, err
			}
			ltBool, err := lt.BoolValue()
			if err != nil {
				return nil, err
			}
			if ltBool {
				res = arg
			}
		}
		return res, nil
	},
	ValidateArgsMin(1),
)


================================================
FILE: execution/func_min_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncMin(t *testing.T) {
	t.Run("int", testCase{
		s:   `min(1, 2, 3)`,
		out: model.NewIntValue(1),
	}.run)
	t.Run("float", testCase{
		s:   `min(1f, 2.5, 3.5)`,
		out: model.NewFloatValue(1),
	}.run)
	t.Run("mixed", testCase{
		s:   `min(1, 2f)`,
		out: model.NewIntValue(1),
	}.run)
}


================================================
FILE: execution/func_parse.go
================================================
package execution

import (
	"context"
	"github.com/tomwright/dasel/v3/model"
	"github.com/tomwright/dasel/v3/parsing"
)

// FuncParse parses the given data at runtime.
var FuncParse = NewFunc(
	"parse",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		var format parsing.Format
		var content []byte
		{
			strVal, err := args[0].StringValue()
			if err != nil {
				return nil, err
			}
			format = parsing.Format(strVal)
		}
		{
			strVal, err := args[1].StringValue()
			if err != nil {
				return nil, err
			}
			content = []byte(strVal)
		}

		reader, err := format.NewReader(parsing.DefaultReaderOptions())
		if err != nil {
			return nil, err
		}

		doc, err := reader.Read(content)
		if err != nil {
			return nil, err
		}

		return doc, nil
	},
	ValidateArgsExactly(2),
)


================================================
FILE: execution/func_parse_test.go
================================================
package execution_test

import (
	"github.com/tomwright/dasel/v3/model"
	_ "github.com/tomwright/dasel/v3/parsing/json"
	"testing"
)

func TestFuncParse(t *testing.T) {
	t.Run("json", testCase{
		s:   `parse('json', '{"foo":"bar"}').foo`,
		out: model.NewStringValue("bar"),
	}.run)
}


================================================
FILE: execution/func_readfile.go
================================================
package execution

import (
	"context"
	"fmt"
	"github.com/tomwright/dasel/v3/model"
	"io"
	"os"
)

// FuncReadFile reads the given filepath at runtime.
var FuncReadFile = NewFunc(
	"readFile",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		filepath, err := args[0].StringValue()
		if err != nil {
			return nil, fmt.Errorf("readFile: %w", err)
		}

		f, err := os.Open(filepath)
		if err != nil {
			return nil, fmt.Errorf("readFile: %w", err)
		}
		defer func() {
			_ = f.Close()
		}()

		fileBytes, err := io.ReadAll(f)
		if err != nil {
			return nil, fmt.Errorf("readFile: %w", err)
		}

		return model.NewStringValue(string(fileBytes)), nil
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_replace.go
================================================
package execution

import (
	"context"
	"strings"

	"github.com/tomwright/dasel/v3/model"
)

// FuncReplace is a function that replaces all occurrences of a substring with another string.
var FuncReplace = NewFunc(
	"replace",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		inputData := data
		if len(args)%2 != 0 {
			inputData = args[0]
			args = args[1:]
		}

		argStrings := make([]string, len(args))
		for i, arg := range args {
			s, err := arg.StringValue()
			if err != nil {
				return nil, err
			}
			argStrings[i] = s
		}
		replacer := strings.NewReplacer(argStrings...)

		inputString, err := inputData.StringValue()
		if err != nil {
			return nil, err
		}

		outputString := replacer.Replace(inputString)

		return model.NewStringValue(outputString), nil
	},
	ValidateArgsMin(2),
)


================================================
FILE: execution/func_replace_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncReplace(t *testing.T) {
	t.Run("input arg", testCase{
		s:   `replace("hello world", "world", "there")`,
		out: model.NewStringValue("hello there"),
	}.run)

	t.Run("multiple data arg", testCase{
		s:   `replace("hello world", "o", "0", "l", "1")`,
		out: model.NewStringValue("he110 w0r1d"),
	}.run)

	t.Run("data arg", testCase{
		s:   `"hello world".replace("o", "0")`,
		out: model.NewStringValue("hell0 w0rld"),
	}.run)

	t.Run("multiple data arg", testCase{
		s:   `"hello world".replace("o", "0", "h", "H")`,
		out: model.NewStringValue("Hell0 w0rld"),
	}.run)

	t.Run("data arg with input arg ignores data arg", testCase{
		s:   `"bob".replace("hello world", "o", "0", "world", "there")`,
		out: model.NewStringValue("hell0 there"),
	}.run)
}


================================================
FILE: execution/func_reverse.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

// FuncReverse is a function that reverses the input.
var FuncReverse = NewFunc(
	"reverse",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		arg := args[0]

		switch arg.Type() {
		case model.TypeString:
			return arg.StringIndexRange(-1, 0)
		case model.TypeSlice:
			return arg.SliceIndexRange(-1, 0)
		default:
			return nil, fmt.Errorf("reverse expects a slice or string, got %s", arg.Type())
		}
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_reverse_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncReverse(t *testing.T) {
	t.Run("array", testCase{
		s: `reverse([1, 2, 3])`,
		outFn: func() *model.Value {
			res := model.NewSliceValue()
			if err := res.Append(model.NewIntValue(3)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := res.Append(model.NewIntValue(2)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if err := res.Append(model.NewIntValue(1)); err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			return res
		},
	}.run)

	t.Run("string", testCase{
		s:   `reverse("hello")`,
		out: model.NewStringValue("olleh"),
	}.run)
}


================================================
FILE: execution/func_sum.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

// FuncSum is a function that returns the sum of the given numbers.
var FuncSum = NewFunc(
	"sum",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		returnType := model.TypeInt

		for _, arg := range args {
			if arg.IsInt() {
				continue
			}
			if arg.IsFloat() {
				returnType = model.TypeFloat
				break
			}
			return nil, fmt.Errorf("cannot sum non-numeric value of type %s", arg.Type().String())
		}

		switch returnType {
		case model.TypeInt:
			var sum int64
			for _, arg := range args {
				if arg.IsInt() {
					intVal, err := arg.IntValue()
					if err != nil {
						return nil, err
					}
					sum += intVal
					continue
				}

				floatVal, err := arg.FloatValue()
				if err != nil {
					return nil, err
				}
				sum += int64(floatVal)
			}
			return model.NewIntValue(sum), nil
		case model.TypeFloat:
			var sum float64
			for _, arg := range args {
				if arg.IsInt() {
					intVal, err := arg.IntValue()
					if err != nil {
						return nil, err
					}
					sum += float64(intVal)
					continue
				}

				floatVal, err := arg.FloatValue()
				if err != nil {
					return nil, err
				}
				sum += floatVal
			}
			return model.NewFloatValue(sum), nil
		default:
			return nil, fmt.Errorf("unsupported return type %s", returnType.String())
		}
	},
	ValidateArgsMin(1),
)


================================================
FILE: execution/func_sum_test.go
================================================
package execution_test

import (
	"github.com/tomwright/dasel/v3/model"
	"testing"
)

func TestFuncSum(t *testing.T) {
	t.Run("int", testCase{
		s:   `sum(1, 2, 3)`,
		out: model.NewIntValue(6),
	}.run)
	t.Run("float", testCase{
		s:   `sum(1.1, 2.2, 3.3)`,
		out: model.NewFloatValue(6.6),
	}.run)
	t.Run("negative int", testCase{
		s:   `sum(-1, -2, -3)`,
		out: model.NewIntValue(-6),
	}.run)
	t.Run("negative float", testCase{
		s:   `sum(-1.1, -2.2, -3.3)`,
		out: model.NewFloatValue(-6.6),
	}.run)
	t.Run("using int and float together returns float", testCase{
		s:   `sum(1, 1.1)`,
		out: model.NewFloatValue(2.1),
	}.run)
}


================================================
FILE: execution/func_to_float.go
================================================
package execution

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tomwright/dasel/v3/model"
)

// FuncToFloat is a function that converts the given value to a string.
var FuncToFloat = NewFunc(
	"toFloat",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		switch args[0].Type() {
		case model.TypeString:
			stringValue, err := args[0].StringValue()
			if err != nil {
				return nil, err
			}

			i, err := strconv.ParseFloat(stringValue, 64)
			if err != nil {
				return nil, err
			}

			return model.NewFloatValue(i), nil
		case model.TypeInt:
			i, err := args[0].IntValue()
			if err != nil {
				return nil, err
			}
			return model.NewFloatValue(float64(i)), nil
		case model.TypeFloat:
			return args[0], nil
		case model.TypeBool:
			i, err := args[0].BoolValue()
			if err != nil {
				return nil, err
			}
			if i {
				return model.NewFloatValue(1), nil
			}
			return model.NewFloatValue(0), nil
		default:
			return nil, fmt.Errorf("cannot convert %s to float", args[0].Type())
		}
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_to_float_test.go
================================================
package execution_test

import (
	"github.com/tomwright/dasel/v3/model"
	"testing"
)

func TestFuncToFloat(t *testing.T) {
	t.Run("string", testCase{
		s:   `toFloat("1.1")`,
		out: model.NewFloatValue(1.1),
	}.run)
	t.Run("int", testCase{
		s:   `toFloat(1)`,
		out: model.NewFloatValue(1),
	}.run)
	t.Run("float", testCase{
		s:   `toFloat(1.1)`,
		out: model.NewFloatValue(1.1),
	}.run)
	t.Run("bool", testCase{
		s:   `toFloat(true)`,
		out: model.NewFloatValue(1),
	}.run)
}


================================================
FILE: execution/func_to_int.go
================================================
package execution

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tomwright/dasel/v3/model"
)

// FuncToInt is a function that converts the given value to a string.
var FuncToInt = NewFunc(
	"toInt",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		switch args[0].Type() {
		case model.TypeString:
			stringValue, err := args[0].StringValue()
			if err != nil {
				return nil, err
			}

			i, err := strconv.ParseInt(stringValue, 10, 64)
			if err != nil {
				return nil, err
			}

			return model.NewIntValue(i), nil
		case model.TypeInt:
			return args[0], nil
		case model.TypeFloat:
			i, err := args[0].FloatValue()
			if err != nil {
				return nil, err
			}
			return model.NewIntValue(int64(i)), nil
		case model.TypeBool:
			i, err := args[0].BoolValue()
			if err != nil {
				return nil, err
			}
			if i {
				return model.NewIntValue(1), nil
			}
			return model.NewIntValue(0), nil
		default:
			return nil, fmt.Errorf("cannot convert %s to int", args[0].Type())
		}
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_to_int_test.go
================================================
package execution_test

import (
	"github.com/tomwright/dasel/v3/model"
	"testing"
)

func TestFuncToInt(t *testing.T) {
	t.Run("string", testCase{
		s:   `toInt("2")`,
		out: model.NewIntValue(2),
	}.run)
	t.Run("int", testCase{
		s:   `toInt(1)`,
		out: model.NewIntValue(1),
	}.run)
	t.Run("float", testCase{
		s:   `toInt(1.1)`,
		out: model.NewIntValue(1),
	}.run)
	t.Run("bool", testCase{
		s:   `toInt(true)`,
		out: model.NewIntValue(1),
	}.run)
}


================================================
FILE: execution/func_to_string.go
================================================
package execution

import (
	"context"
	"fmt"

	"github.com/tomwright/dasel/v3/model"
)

// FuncToString is a function that converts the given value to a string.
var FuncToString = NewFunc(
	"toString",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		switch args[0].Type() {
		case model.TypeString:
			stringValue, err := args[0].StringValue()
			if err != nil {
				return nil, err
			}
			model.NewStringValue(stringValue)
			return args[0], nil
		case model.TypeInt:
			i, err := args[0].IntValue()
			if err != nil {
				return nil, err
			}
			return model.NewStringValue(fmt.Sprintf("%d", i)), nil
		case model.TypeFloat:
			i, err := args[0].FloatValue()
			if err != nil {
				return nil, err
			}
			return model.NewStringValue(fmt.Sprintf("%g", i)), nil
		case model.TypeBool:
			i, err := args[0].BoolValue()
			if err != nil {
				return nil, err
			}
			return model.NewStringValue(fmt.Sprintf("%v", i)), nil
		default:
			return nil, fmt.Errorf("cannot convert %s to string", args[0].Type())
		}
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_to_string_test.go
================================================
package execution_test

import (
	"github.com/tomwright/dasel/v3/model"
	"testing"
)

func TestFuncToString(t *testing.T) {
	t.Run("string", testCase{
		s:   `toString("Hello")`,
		out: model.NewStringValue("Hello"),
	}.run)
	t.Run("int", testCase{
		s:   `toString(1)`,
		out: model.NewStringValue("1"),
	}.run)
	t.Run("float", testCase{
		s:   `toString(1.1)`,
		out: model.NewStringValue("1.1"),
	}.run)
	t.Run("bool", testCase{
		s:   `toString(true)`,
		out: model.NewStringValue("true"),
	}.run)
}


================================================
FILE: execution/func_type_of.go
================================================
package execution

import (
	"context"
	"github.com/tomwright/dasel/v3/model"
)

// FuncTypeOf is a function that returns the type of the first argument as a string.
var FuncTypeOf = NewFunc(
	"typeOf",
	func(ctx context.Context, data *model.Value, args model.Values) (*model.Value, error) {
		return model.NewStringValue(args[0].Type().String()), nil
	},
	ValidateArgsExactly(1),
)


================================================
FILE: execution/func_type_of_test.go
================================================
package execution_test

import (
	"testing"

	"github.com/tomwright/dasel/v3/model"
)

func TestFuncTypeOf(t *testing.T) {
	t.Run("string", testCase{
		s:   `typeOf("hello")`,
		out: model.NewStringValue("string"),
	}.run)
	t.Run("int", testCase{
		s:   `typeOf(123)`,
		out: model.NewStringValue("int"),
	}.run)
	t.Run("float", testCase{
		s:   `typeOf(12.3)`,
		out: model.NewStringValue("float"),
	}.run)
	t.Run("bool", testCase{
		s:   `typeOf(true)`,
		out: model.NewStringValue("bool"),
	}.run)
	t.Run("array", testCase{
		s:   `typeOf([])`,
		out: model.NewStringValue("array"),
	}.run)
	t.Run("map", testCase{
		s:   `typeOf({})`,
		out: model.NewStringValue("map"),
	}.run)
	t.Run("null", testCase{
		s:   `typeOf(null)`,
		out: model.NewStringValue("null"),
	}.run)
}


================================================
FILE: execution/options.go
================================================
package execution

import "github.com/tomwright/dasel/v3/model"

// ExecuteOptionFn is a function that can be used to set options on the execution of the selector.
type ExecuteOptionFn func(*Options)

// Options contains the options for the execution of the selector.
type Options struct {
	Funcs    FuncCollection
	Vars     map[string]*model.Value
	Unstable bool
}

// NewOptions creates a new Options struct with the given options.
func NewOptions(opts ...ExecuteOptionFn) *Options {
	o := &Options{
		Funcs: DefaultFuncCollection,
		Vars:  map[string]*model.Value{},
	}
	for _, opt := range opts {
		if opt == nil {
			continue
		}
		opt(o)
	}
	return o
}

// WithFuncs sets the functions that can be used in the selector.
func WithFuncs(fc FuncCollection) ExecuteOptionFn {
	return func(o *Options) {
		o.Funcs = fc
	}
}

// WithVariable sets a variable for use in the selector.
func WithVariable(key string, val *model.Value) ExecuteOptionFn {
	return func(o *Options) {
		o.Vars[key] = val
	}
}

// WithUnstable allows access to potentially unstable features.
func WithUnstable() ExecuteOptionFn {
	return func(o *Options) {
		o.Unstable = true
	}
}

// WithoutUnstable disallows access to potentially unstable features.
func WithoutUnstable() ExecuteOptionFn {
	return func(o *Options) {
		o.Unstable = false
	}
}


================================================
FILE: go.mod
================================================
module github.com/tomwright/dasel/v3

go 1.25

require (
	github.com/alecthomas/kong v1.14.0
	github.com/charmbracelet/bubbles v1.0.0
	github.com/charmbracelet/bubbletea v1.3.10
	github.com/charmbracelet/lipgloss v1.1.0
	github.com/goccy/go-json v0.10.5
	github.com/google/go-cmp v0.7.0
	github.com/hashicorp/hcl/v2 v2.24.0
	github.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753
	github.com/zclconf/go-cty v1.17.0
	go.yaml.in/yaml/v4 v4.0.0-rc.3
	gopkg.in/ini.v1 v1.67.0
)

require (
	github.com/agext/levenshtein v1.2.1 // indirect
	github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
	github.com/atotto/clipboard v0.1.4 // indirect
	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
	github.com/charmbracelet/colorprofile v0.4.1 // indirect
	github.com/charmbracelet/x/ansi v0.11.6 // indirect
	github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
	github.com/charmbracelet/x/term v0.2.2 // indirect
	github.com/clipperhouse/displaywidth v0.9.0 // indirect
	github.com/clipperhouse/stringish v0.1.1 // indirect
	github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
	github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/mattn/go-localereader v0.0.1 // indirect
	github.com/mattn/go-runewidth v0.0.19 // indirect
	github.com/mitchellh/go-wordwrap v1.0.1 // indirect
	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
	github.com/muesli/cancelreader v0.2.2 // indirect
	github.com/muesli/termenv v0.16.0 // indirect
	github.com/rivo/uniseg v0.4.7 // indirect
	github.com/stretchr/testify v1.11.1 // indirect
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
	golang.org/x/mod v0.26.0 // indirect
	golang.org/x/sync v0.16.0 // indirect
	golang.org/x/sys v0.38.0 // indirect
	golang.org/x/text v0.28.0 // indirect
	golang.org/x/tools v0.35.0 // indirect
)


================================================
FILE: go.sum
================================================
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753 h1:aTpyfgn3dz2npHl011BHQehdSavqjzhZdE6fJuJlO3A=
github.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: internal/cli/command.go
================================================
package cli

import (
	"errors"
	"io"
	"reflect"

	"github.com/alecthomas/kong"
	"github.com/tomwright/dasel/v3/internal"
)

var ErrNoArgsGiven = errors.New("no arguments given")

type Globals struct {
	Stdin       io.Reader        `kong:"-"`
	Stdout      io.Writer        `kong:"-"`
	Stderr      io.Writer        `kong:"-"`
	helpPrinter kong.HelpPrinter `kong:"-"`
}

type CLI struct {
	Globals

	Query       QueryCmd       `cmd:"" default:"withargs" help:"[default] Execute a query"`
	Version     VersionCmd     `cmd:"" help:"Print the version"`
	Interactive InteractiveCmd `cmd:"" help:"Start an interactive session (alpha)"`
}

func MustRun(stdin io.Reader, stdout, stderr io.Writer) {
	ctx, err := Run(stdin, stdout, stderr)
	if err == nil {
		return
	}

	if ctx == nil {
		panic(err)
	}

	ctx.Errorf("%s", err.Error())
	if errors.Is(err, ErrNoArgsGiven) {
		if err := ctx.PrintUsage(false); err != nil {
			panic(err
Download .txt
gitextract_rq16o89b/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── build-dev.yaml
│       ├── build-test.yaml
│       ├── build.yaml
│       ├── bump-homebrew.yaml
│       ├── codeql-analysis.yml
│       ├── container.yaml
│       ├── golangci-lint.yaml
│       └── test.yaml
├── .gitignore
├── .golangci.yaml
├── .pre-commit-hooks.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── api.go
├── api_example_test.go
├── api_test.go
├── codecov.yaml
├── execution/
│   ├── README.md
│   ├── context.go
│   ├── execute.go
│   ├── execute_array.go
│   ├── execute_array_test.go
│   ├── execute_assign.go
│   ├── execute_assign_test.go
│   ├── execute_binary.go
│   ├── execute_binary_test.go
│   ├── execute_branch.go
│   ├── execute_branch_test.go
│   ├── execute_conditional.go
│   ├── execute_conditional_test.go
│   ├── execute_each.go
│   ├── execute_each_test.go
│   ├── execute_filter.go
│   ├── execute_filter_test.go
│   ├── execute_func.go
│   ├── execute_func_test.go
│   ├── execute_literal.go
│   ├── execute_literal_test.go
│   ├── execute_map.go
│   ├── execute_map_test.go
│   ├── execute_object.go
│   ├── execute_object_test.go
│   ├── execute_recursive_descent.go
│   ├── execute_search.go
│   ├── execute_sort_by.go
│   ├── execute_sort_by_test.go
│   ├── execute_spread.go
│   ├── execute_spread_test.go
│   ├── execute_test.go
│   ├── execute_unary.go
│   ├── execute_unary_test.go
│   ├── func.go
│   ├── func_add.go
│   ├── func_add_test.go
│   ├── func_base64.go
│   ├── func_contains.go
│   ├── func_contains_test.go
│   ├── func_get.go
│   ├── func_get_test.go
│   ├── func_has.go
│   ├── func_has_test.go
│   ├── func_ignore.go
│   ├── func_join.go
│   ├── func_join_test.go
│   ├── func_keys.go
│   ├── func_keys_test.go
│   ├── func_len.go
│   ├── func_len_test.go
│   ├── func_max.go
│   ├── func_max_test.go
│   ├── func_merge.go
│   ├── func_merge_test.go
│   ├── func_min.go
│   ├── func_min_test.go
│   ├── func_parse.go
│   ├── func_parse_test.go
│   ├── func_readfile.go
│   ├── func_replace.go
│   ├── func_replace_test.go
│   ├── func_reverse.go
│   ├── func_reverse_test.go
│   ├── func_sum.go
│   ├── func_sum_test.go
│   ├── func_to_float.go
│   ├── func_to_float_test.go
│   ├── func_to_int.go
│   ├── func_to_int_test.go
│   ├── func_to_string.go
│   ├── func_to_string_test.go
│   ├── func_type_of.go
│   ├── func_type_of_test.go
│   └── options.go
├── go.mod
├── go.sum
├── internal/
│   ├── cli/
│   │   ├── command.go
│   │   ├── command_test.go
│   │   ├── config.go
│   │   ├── generic_test.go
│   │   ├── interactive.go
│   │   ├── interactive_tea.go
│   │   ├── interactive_tea_input.go
│   │   ├── interactive_tea_output.go
│   │   ├── query.go
│   │   ├── read_write_flag.go
│   │   ├── run.go
│   │   ├── variable.go
│   │   └── version.go
│   ├── ptr/
│   │   ├── to.go
│   │   └── to_test.go
│   └── version.go
├── model/
│   ├── README.md
│   ├── error.go
│   ├── go_value.go
│   ├── go_value_test.go
│   ├── orderedmap/
│   │   └── map.go
│   ├── value.go
│   ├── value_comparison.go
│   ├── value_comparison_test.go
│   ├── value_literal.go
│   ├── value_literal_test.go
│   ├── value_map.go
│   ├── value_map_test.go
│   ├── value_math.go
│   ├── value_math_test.go
│   ├── value_metadata.go
│   ├── value_metadata_test.go
│   ├── value_set.go
│   ├── value_set_test.go
│   ├── value_slice.go
│   ├── value_slice_test.go
│   └── value_test.go
├── parsing/
│   ├── csv/
│   │   ├── csv.go
│   │   ├── csv_test.go
│   │   ├── reader.go
│   │   ├── reader_test.go
│   │   ├── writer.go
│   │   └── writer_test.go
│   ├── d/
│   │   └── reader.go
│   ├── format.go
│   ├── hcl/
│   │   ├── hcl.go
│   │   ├── reader.go
│   │   ├── reader_test.go
│   │   ├── writer.go
│   │   └── writer_test.go
│   ├── ini/
│   │   ├── ini.go
│   │   ├── ini_reader.go
│   │   ├── ini_test.go
│   │   └── ini_writer.go
│   ├── json/
│   │   ├── json.go
│   │   ├── json_reader.go
│   │   ├── json_test.go
│   │   └── json_writer.go
│   ├── reader.go
│   ├── toml/
│   │   ├── testdata/
│   │   │   └── complex_example.toml
│   │   ├── toml.go
│   │   ├── toml_reader.go
│   │   ├── toml_reader_test.go
│   │   ├── toml_writer.go
│   │   └── toml_writer_test.go
│   ├── writer.go
│   ├── xml/
│   │   ├── reader.go
│   │   ├── reader_test.go
│   │   ├── structured_comment_test.go
│   │   ├── writer.go
│   │   ├── writer_internal_test.go
│   │   ├── writer_test.go
│   │   └── xml.go
│   └── yaml/
│       ├── yaml.go
│       ├── yaml_reader.go
│       ├── yaml_test.go
│       └── yaml_writer.go
└── selector/
    ├── README.md
    ├── ast/
    │   ├── ast.go
    │   ├── ast_test.go
    │   ├── expression_complex.go
    │   └── expression_literal.go
    ├── lexer/
    │   ├── token.go
    │   ├── tokenize.go
    │   └── tokenize_test.go
    ├── parser/
    │   ├── denotations.go
    │   ├── error.go
    │   ├── parse_array.go
    │   ├── parse_branch.go
    │   ├── parse_each.go
    │   ├── parse_filter.go
    │   ├── parse_func.go
    │   ├── parse_group.go
    │   ├── parse_if.go
    │   ├── parse_literal.go
    │   ├── parse_map.go
    │   ├── parse_object.go
    │   ├── parse_recursive_descent.go
    │   ├── parse_search.go
    │   ├── parse_sort_by.go
    │   ├── parse_symbol.go
    │   ├── parse_variable.go
    │   ├── parser.go
    │   ├── parser_binary.go
    │   └── parser_test.go
    └── parser.go
Download .txt
SYMBOL INDEX (723 symbols across 159 files)

FILE: api.go
  function Query (line 11) | func Query(ctx context.Context, data any, selector string, opts ...execu...
  function Select (line 35) | func Select(ctx context.Context, data any, selector string, opts ...exec...
  function Modify (line 53) | func Modify(ctx context.Context, data any, selector string, newValue any...

FILE: api_example_test.go
  function ExampleSelect (line 10) | func ExampleSelect() {

FILE: api_test.go
  type modifyTestCase (line 10) | type modifyTestCase struct
    method run (line 18) | func (tc modifyTestCase) run(t *testing.T) {
  function TestQuery (line 31) | func TestQuery(t *testing.T) {
  function TestSelect (line 59) | func TestSelect(t *testing.T) {
  function TestModify (line 81) | func TestModify(t *testing.T) {

FILE: execution/context.go
  type ctxKey (line 8) | type ctxKey
  constant executorIDCtxKey (line 11) | executorIDCtxKey    ctxKey = "executorID"
  constant executorPathCtxKey (line 12) | executorPathCtxKey  ctxKey = "executorPath"
  constant executorDepthCtxKey (line 13) | executorDepthCtxKey ctxKey = "executorDepth"
  function WithExecutorID (line 16) | func WithExecutorID(ctx context.Context, executorID string) context.Cont...
  function ExecutorID (line 27) | func ExecutorID(ctx context.Context) string {
  function ExecutorPath (line 35) | func ExecutorPath(ctx context.Context) string {
  function ExecutorDepth (line 43) | func ExecutorDepth(ctx context.Context) int {

FILE: execution/execute.go
  function ExecuteSelector (line 16) | func ExecuteSelector(ctx context.Context, selectorStr string, value *mod...
  type expressionExecutor (line 34) | type expressionExecutor
  function ExecuteAST (line 37) | func ExecuteAST(ctx context.Context, expr ast.Expr, value *model.Value, ...
  function exprExecutor (line 87) | func exprExecutor(options *Options, expr ast.Expr) (expressionExecutor, ...
  function chainedExprExecutor (line 156) | func chainedExprExecutor(e ast.ChainedExpr) (expressionExecutor, error) {
  function variableExprExecutor (line 171) | func variableExprExecutor(e ast.VariableExpr) (expressionExecutor, error) {

FILE: execution/execute_array.go
  function arrayExprExecutor (line 11) | func arrayExprExecutor(e ast.ArrayExpr) (expressionExecutor, error) {
  function rangeExprExecutor (line 42) | func rangeExprExecutor(e ast.RangeExpr) (expressionExecutor, error) {
  function indexExprExecutor (line 90) | func indexExprExecutor(e ast.IndexExpr) (expressionExecutor, error) {

FILE: execution/execute_array_test.go
  function TestArray (line 9) | func TestArray(t *testing.T) {

FILE: execution/execute_assign.go
  function executeAssign (line 10) | func executeAssign(ctx context.Context, left *model.Value, right *model....

FILE: execution/execute_assign_test.go
  function TestAssignVariable (line 10) | func TestAssignVariable(t *testing.T) {

FILE: execution/execute_binary.go
  type binaryExpressionExecutorFn (line 13) | type binaryExpressionExecutorFn
  function basicBinaryExpressionExecutorFn (line 15) | func basicBinaryExpressionExecutorFn(handler func(ctx context.Context, l...
  function binaryExprExecutor (line 58) | func binaryExprExecutor(e ast.BinaryExpr) (expressionExecutor, error) {
  function init (line 74) | func init() {

FILE: execution/execute_binary_test.go
  function TestBinary (line 10) | func TestBinary(t *testing.T) {

FILE: execution/execute_branch.go
  function branchExprExecutor (line 11) | func branchExprExecutor(e ast.BranchExpr) (expressionExecutor, error) {

FILE: execution/execute_branch_test.go
  function TestBranch (line 10) | func TestBranch(t *testing.T) {

FILE: execution/execute_conditional.go
  function conditionalExprExecutor (line 11) | func conditionalExprExecutor(e ast.ConditionalExpr) (expressionExecutor,...

FILE: execution/execute_conditional_test.go
  function TestConditional (line 9) | func TestConditional(t *testing.T) {

FILE: execution/execute_each.go
  function eachExprExecutor (line 11) | func eachExprExecutor(e ast.EachExpr) (expressionExecutor, error) {

FILE: execution/execute_each_test.go
  function TestEach (line 9) | func TestEach(t *testing.T) {

FILE: execution/execute_filter.go
  function filterExprExecutor (line 11) | func filterExprExecutor(e ast.FilterExpr) (expressionExecutor, error) {

FILE: execution/execute_filter_test.go
  function TestFilter (line 9) | func TestFilter(t *testing.T) {

FILE: execution/execute_func.go
  function prepareArgs (line 13) | func prepareArgs(ctx context.Context, opts *Options, data *model.Value, ...
  function callFnExecutor (line 31) | func callFnExecutor(f FuncFn, argsE ast.Expressions) (expressionExecutor...
  function callExprExecutor (line 52) | func callExprExecutor(options *Options, e ast.CallExpr) (expressionExecu...

FILE: execution/execute_func_test.go
  function TestFunc (line 11) | func TestFunc(t *testing.T) {

FILE: execution/execute_literal.go
  function numberIntExprExecutor (line 9) | func numberIntExprExecutor(e ast.NumberIntExpr) (expressionExecutor, err...
  function numberFloatExprExecutor (line 16) | func numberFloatExprExecutor(e ast.NumberFloatExpr) (expressionExecutor,...
  function stringExprExecutor (line 23) | func stringExprExecutor(e ast.StringExpr) (expressionExecutor, error) {
  function boolExprExecutor (line 30) | func boolExprExecutor(e ast.BoolExpr) (expressionExecutor, error) {

FILE: execution/execute_literal_test.go
  function TestLiteral (line 9) | func TestLiteral(t *testing.T) {

FILE: execution/execute_map.go
  function mapExprExecutor (line 11) | func mapExprExecutor(e ast.MapExpr) (expressionExecutor, error) {

FILE: execution/execute_map_test.go
  function TestMap (line 10) | func TestMap(t *testing.T) {

FILE: execution/execute_object.go
  function objectExprExecutor (line 11) | func objectExprExecutor(e ast.ObjectExpr) (expressionExecutor, error) {
  function propertyExprExecutor (line 66) | func propertyExprExecutor(e ast.PropertyExpr) (expressionExecutor, error) {

FILE: execution/execute_object_test.go
  function TestObject (line 10) | func TestObject(t *testing.T) {

FILE: execution/execute_recursive_descent.go
  function recursiveDescentExprExecutor2 (line 10) | func recursiveDescentExprExecutor2(e ast.RecursiveDescentExpr) (expressi...

FILE: execution/execute_search.go
  function searchExprExecutor (line 10) | func searchExprExecutor(e ast.SearchExpr) (expressionExecutor, error) {

FILE: execution/execute_sort_by.go
  function sortByExprExecutor (line 12) | func sortByExprExecutor(e ast.SortByExpr) (expressionExecutor, error) {

FILE: execution/execute_sort_by_test.go
  function TestFuncSortBy (line 9) | func TestFuncSortBy(t *testing.T) {

FILE: execution/execute_spread.go
  function spreadExprExecutor (line 10) | func spreadExprExecutor() (expressionExecutor, error) {
  function prepareSpreadValues (line 45) | func prepareSpreadValues(val *model.Value) (model.Values, error) {

FILE: execution/execute_spread_test.go
  function TestSpread (line 9) | func TestSpread(t *testing.T) {

FILE: execution/execute_test.go
  type testCase (line 13) | type testCase struct
    method run (line 23) | func (tc testCase) run(t *testing.T) {
  function TestExecuteSelector_HappyPath (line 59) | func TestExecuteSelector_HappyPath(t *testing.T) {

FILE: execution/execute_unary.go
  function unaryExprExecutor (line 12) | func unaryExprExecutor(e ast.UnaryExpr) (expressionExecutor, error) {

FILE: execution/execute_unary_test.go
  function TestUnary (line 10) | func TestUnary(t *testing.T) {

FILE: execution/func.go
  type ArgsValidator (line 39) | type ArgsValidator
  function ValidateArgsExactly (line 42) | func ValidateArgsExactly(expected int) ArgsValidator {
  function ValidateArgsMin (line 52) | func ValidateArgsMin(expected int) ArgsValidator {
  function ValidateArgsMax (line 62) | func ValidateArgsMax(expected int) ArgsValidator {
  function ValidateArgsMinMax (line 72) | func ValidateArgsMinMax(min int, max int) ArgsValidator {
  type Func (line 82) | type Func struct
    method Handler (line 89) | func (f *Func) Handler() FuncFn {
  function NewFunc (line 105) | func NewFunc(name string, handler FuncFn, argsValidator ArgsValidator) *...
  type FuncFn (line 114) | type FuncFn
  type FuncCollection (line 117) | type FuncCollection
    method Register (line 125) | func (fc FuncCollection) Register(funcs ...*Func) FuncCollection {
    method Get (line 133) | func (fc FuncCollection) Get(name string) (FuncFn, bool) {
    method Delete (line 139) | func (fc FuncCollection) Delete(names ...string) FuncCollection {
    method Copy (line 147) | func (fc FuncCollection) Copy() FuncCollection {
  function NewFuncCollection (line 120) | func NewFuncCollection(funcs ...*Func) FuncCollection {

FILE: execution/func_add_test.go
  function TestFuncAdd (line 10) | func TestFuncAdd(t *testing.T) {

FILE: execution/func_contains_test.go
  function TestFuncContains (line 9) | func TestFuncContains(t *testing.T) {

FILE: execution/func_get_test.go
  function TestFuncGet (line 10) | func TestFuncGet(t *testing.T) {

FILE: execution/func_has_test.go
  function TestFuncHas (line 9) | func TestFuncHas(t *testing.T) {

FILE: execution/func_join_test.go
  function TestFuncJoin (line 10) | func TestFuncJoin(t *testing.T) {

FILE: execution/func_keys_test.go
  function TestFuncKeys (line 10) | func TestFuncKeys(t *testing.T) {

FILE: execution/func_len_test.go
  function TestFuncLen (line 9) | func TestFuncLen(t *testing.T) {

FILE: execution/func_max_test.go
  function TestFuncMax (line 9) | func TestFuncMax(t *testing.T) {

FILE: execution/func_merge_test.go
  function TestFuncMerge (line 9) | func TestFuncMerge(t *testing.T) {

FILE: execution/func_min_test.go
  function TestFuncMin (line 9) | func TestFuncMin(t *testing.T) {

FILE: execution/func_parse_test.go
  function TestFuncParse (line 9) | func TestFuncParse(t *testing.T) {

FILE: execution/func_replace_test.go
  function TestFuncReplace (line 9) | func TestFuncReplace(t *testing.T) {

FILE: execution/func_reverse_test.go
  function TestFuncReverse (line 9) | func TestFuncReverse(t *testing.T) {

FILE: execution/func_sum_test.go
  function TestFuncSum (line 8) | func TestFuncSum(t *testing.T) {

FILE: execution/func_to_float_test.go
  function TestFuncToFloat (line 8) | func TestFuncToFloat(t *testing.T) {

FILE: execution/func_to_int_test.go
  function TestFuncToInt (line 8) | func TestFuncToInt(t *testing.T) {

FILE: execution/func_to_string_test.go
  function TestFuncToString (line 8) | func TestFuncToString(t *testing.T) {

FILE: execution/func_type_of_test.go
  function TestFuncTypeOf (line 9) | func TestFuncTypeOf(t *testing.T) {

FILE: execution/options.go
  type ExecuteOptionFn (line 6) | type ExecuteOptionFn
  type Options (line 9) | type Options struct
  function NewOptions (line 16) | func NewOptions(opts ...ExecuteOptionFn) *Options {
  function WithFuncs (line 31) | func WithFuncs(fc FuncCollection) ExecuteOptionFn {
  function WithVariable (line 38) | func WithVariable(key string, val *model.Value) ExecuteOptionFn {
  function WithUnstable (line 45) | func WithUnstable() ExecuteOptionFn {
  function WithoutUnstable (line 52) | func WithoutUnstable() ExecuteOptionFn {

FILE: internal/cli/command.go
  type Globals (line 14) | type Globals struct
  type CLI (line 21) | type CLI struct
  function MustRun (line 29) | func MustRun(stdin io.Reader, stdout, stderr io.Writer) {
  function Run (line 49) | func Run(stdin io.Reader, stdout, stderr io.Writer) (*kong.Context, erro...

FILE: internal/cli/command_test.go
  function runDasel (line 13) | func runDasel(args []string, in []byte) ([]byte, []byte, error) {
  type testCase (line 30) | type testCase struct
  function runTest (line 38) | func runTest(tc testCase) func(t *testing.T) {
  function TestRun (line 63) | func TestRun(t *testing.T) {

FILE: internal/cli/config.go
  type Config (line 15) | type Config struct
  function LoadConfig (line 26) | func LoadConfig(path string) (Config, error) {

FILE: internal/cli/generic_test.go
  function newStringWithFormat (line 14) | func newStringWithFormat(format parsing.Format, data string) bytesWithFo...
  type bytesWithFormat (line 21) | type bytesWithFormat struct
  type testCases (line 26) | type testCases struct
    method run (line 34) | func (tcs testCases) run(t *testing.T) {
  function TestCrossFormatHappyPath (line 62) | func TestCrossFormatHappyPath(t *testing.T) {

FILE: internal/cli/interactive.go
  function NewInteractiveCmd (line 11) | func NewInteractiveCmd(queryCmd *QueryCmd) *InteractiveCmd {
  type InteractiveCmd (line 24) | type InteractiveCmd struct
    method Run (line 37) | func (c *InteractiveCmd) Run(ctx *Globals) error {

FILE: internal/cli/interactive_tea.go
  type interactiveDaselExecutor (line 39) | type interactiveDaselExecutor
  function newInteractiveTeaProgram (line 41) | func newInteractiveTeaProgram(initialInput string, initialSelector strin...
  type interactiveSharedData (line 48) | type interactiveSharedData struct
  type interactiveRootModel (line 55) | type interactiveRootModel struct
    method Init (line 82) | func (m *interactiveRootModel) Init() tea.Cmd {
    method cycleReader (line 104) | func (m *interactiveRootModel) cycleReader() {
    method cycleWriter (line 108) | func (m *interactiveRootModel) cycleWriter() {
    method Update (line 112) | func (m *interactiveRootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    method headerView (line 166) | func (m *interactiveRootModel) headerView() string {
    method inputView (line 181) | func (m *interactiveRootModel) inputView() string {
    method View (line 185) | func (m *interactiveRootModel) View() string {
  function newInteractiveRootModel (line 61) | func newInteractiveRootModel(initialInput string, initialSelector string...
  function cycleFormats (line 86) | func cycleFormats(all []parsing.Format, current parsing.Format) parsing....

FILE: internal/cli/interactive_tea_input.go
  type interactiveInputModel (line 8) | type interactiveInputModel struct
    method Init (line 27) | func (m *interactiveInputModel) Init() tea.Cmd {
    method Update (line 31) | func (m *interactiveInputModel) Update(msg tea.Msg) (tea.Model, tea.Cm...
    method View (line 48) | func (m *interactiveInputModel) View() string {
  function newInteractiveInputModel (line 13) | func newInteractiveInputModel(sharedData *interactiveSharedData) *intera...

FILE: internal/cli/interactive_tea_output.go
  type interactiveOutputModel (line 10) | type interactiveOutputModel struct
    method Init (line 34) | func (m *interactiveOutputModel) Init() tea.Cmd {
    method setOutput (line 38) | func (m *interactiveOutputModel) setOutput(output string) {
    method setSize (line 45) | func (m *interactiveOutputModel) setSize(width int, height int) {
    method setVerticalPosition (line 55) | func (m *interactiveOutputModel) setVerticalPosition(pos int) {
    method Update (line 59) | func (m *interactiveOutputModel) Update(msg tea.Msg) (tea.Model, tea.C...
    method View (line 94) | func (m *interactiveOutputModel) View() string {
  function newInteractiveOutputModel (line 24) | func newInteractiveOutputModel(sharedData *interactiveSharedData, root b...

FILE: internal/cli/query.go
  type QueryCmd (line 5) | type QueryCmd struct
    method Run (line 21) | func (c *QueryCmd) Run(ctx *Globals) error {

FILE: internal/cli/read_write_flag.go
  type extReadWriteFlag (line 12) | type extReadWriteFlag struct
  type extReadWriteFlags (line 17) | type extReadWriteFlags
  function applyReaderFlags (line 19) | func applyReaderFlags(readerOptions *parsing.ReaderOptions, readerFlags ...
  function applyWriterFlags (line 32) | func applyWriterFlags(writerOptions *parsing.WriterOptions, writerFlags ...
  type extReadWriteFlagMapper (line 45) | type extReadWriteFlagMapper struct
    method Decode (line 48) | func (vm *extReadWriteFlagMapper) Decode(ctx *kong.DecodeContext, targ...

FILE: internal/cli/run.go
  type runOpts (line 13) | type runOpts struct
  function run (line 29) | func run(o runOpts) ([]byte, error) {

FILE: internal/cli/variable.go
  type variable (line 16) | type variable struct
  type variables (line 21) | type variables
  function variableOptions (line 23) | func variableOptions(vars variables) []execution.ExecuteOptionFn {
  type variableMapper (line 33) | type variableMapper struct
    method Decode (line 40) | func (vm *variableMapper) Decode(ctx *kong.DecodeContext, target refle...

FILE: internal/cli/version.go
  type VersionCmd (line 5) | type VersionCmd struct
    method Run (line 8) | func (c *VersionCmd) Run(ctx *Globals) error {

FILE: internal/ptr/to.go
  function To (line 4) | func To[T any](v T) *T {

FILE: internal/ptr/to_test.go
  function TestTo (line 8) | func TestTo(t *testing.T) {

FILE: internal/version.go
  function init (line 11) | func init() {

FILE: model/error.go
  type MapKeyNotFound (line 6) | type MapKeyNotFound struct
    method Error (line 11) | func (e MapKeyNotFound) Error() string {
  type SliceIndexOutOfRange (line 16) | type SliceIndexOutOfRange struct
    method Error (line 21) | func (e SliceIndexOutOfRange) Error() string {
  type ErrIncompatibleTypes (line 26) | type ErrIncompatibleTypes struct
    method Error (line 32) | func (e ErrIncompatibleTypes) Error() string {
  type ErrUnexpectedType (line 36) | type ErrUnexpectedType struct
    method Error (line 41) | func (e ErrUnexpectedType) Error() string {
  type ErrUnexpectedTypes (line 45) | type ErrUnexpectedTypes struct
    method Error (line 50) | func (e ErrUnexpectedTypes) Error() string {

FILE: model/go_value.go
  method GoValue (line 6) | func (v *Value) GoValue() (any, error) {

FILE: model/go_value_test.go
  type goValueTestCase (line 9) | type goValueTestCase struct
    method run (line 15) | func (tc goValueTestCase) run(t *testing.T) {
  function TestValue_GoValue (line 28) | func TestValue_GoValue(t *testing.T) {

FILE: model/orderedmap/map.go
  type KeyValue (line 8) | type KeyValue struct
  function NewMap (line 14) | func NewMap() *Map {
  function FromMap (line 26) | func FromMap(source map[string]any) *Map {
  type Map (line 35) | type Map struct
    method Len (line 42) | func (m *Map) Len() int {
    method Equal (line 46) | func (m *Map) Equal(other *Map) bool {
    method Get (line 65) | func (m *Map) Get(key string) (any, bool) {
    method Set (line 71) | func (m *Map) Set(key string, value any) *Map {
    method Delete (line 82) | func (m *Map) Delete(key string) *Map {
    method KeyValues (line 103) | func (m *Map) KeyValues() []KeyValue {
    method Keys (line 115) | func (m *Map) Keys() []string {
    method UnorderedData (line 120) | func (m *Map) UnorderedData() map[string]any {

FILE: model/value.go
  type Type (line 10) | type Type
    method String (line 12) | func (t Type) String() string {
  constant TypeString (line 17) | TypeString  Type = "string"
  constant TypeInt (line 18) | TypeInt     Type = "int"
  constant TypeFloat (line 19) | TypeFloat   Type = "float"
  constant TypeBool (line 20) | TypeBool    Type = "bool"
  constant TypeMap (line 21) | TypeMap     Type = "map"
  constant TypeSlice (line 22) | TypeSlice   Type = "array"
  constant TypeUnknown (line 23) | TypeUnknown Type = "unknown"
  constant TypeNull (line 24) | TypeNull    Type = "null"
  type KeyValue (line 28) | type KeyValue struct
  type Values (line 34) | type Values
    method ToSliceValue (line 37) | func (v Values) ToSliceValue() (*Value, error) {
  type Value (line 48) | type Value struct
    method String (line 56) | func (v *Value) String() string {
    method string (line 64) | func (v *Value) string(indent int) string {
    method isDaselValue (line 154) | func (v *Value) isDaselValue() bool {
    method daselValue (line 162) | func (v *Value) daselValue() (*Value, error) {
    method Interface (line 174) | func (v *Value) Interface() any {
    method Kind (line 182) | func (v *Value) Kind() reflect.Kind {
    method UnpackKinds (line 187) | func (v *Value) UnpackKinds(kinds ...reflect.Kind) *Value {
    method UnpackUntilType (line 214) | func (v *Value) UnpackUntilType(t reflect.Type) (*Value, error) {
    method UnpackUntilAddressable (line 239) | func (v *Value) UnpackUntilAddressable() (*Value, error) {
    method UnpackUntilKind (line 254) | func (v *Value) UnpackUntilKind(k reflect.Kind) (*Value, error) {
    method UnpackUntilKinds (line 269) | func (v *Value) UnpackUntilKinds(kinds ...reflect.Kind) (*Value, error) {
    method Type (line 284) | func (v *Value) Type() Type {
    method IsScalar (line 305) | func (v *Value) IsScalar() bool {
    method Len (line 320) | func (v *Value) Len() (int, error) {
    method Copy (line 345) | func (v *Value) Copy() (*Value, error) {
  function indentStr (line 60) | func indentStr(indent int) string {
  function NewValue (line 123) | func NewValue(v any) *Value {
  function NewNestedValue (line 147) | func NewNestedValue(v *Value) *Value {
  type ErrCouldNotUnpackToType (line 205) | type ErrCouldNotUnpackToType struct
    method Error (line 209) | func (e ErrCouldNotUnpackToType) Error() string {

FILE: model/value_comparison.go
  method Compare (line 4) | func (v *Value) Compare(other *Value) (int, error) {
  method Equal (line 33) | func (v *Value) Equal(other *Value) (*Value, error) {
  method NotEqual (line 69) | func (v *Value) NotEqual(other *Value) (*Value, error) {
  method LessThan (line 82) | func (v *Value) LessThan(other *Value) (*Value, error) {
  method LessThanOrEqual (line 144) | func (v *Value) LessThanOrEqual(other *Value) (*Value, error) {
  method GreaterThan (line 165) | func (v *Value) GreaterThan(other *Value) (*Value, error) {
  method GreaterThanOrEqual (line 178) | func (v *Value) GreaterThanOrEqual(other *Value) (*Value, error) {
  method EqualTypeValue (line 191) | func (v *Value) EqualTypeValue(other *Value) (bool, error) {

FILE: model/value_comparison_test.go
  type compareTestCase (line 9) | type compareTestCase struct
  function TestValue_Equal (line 15) | func TestValue_Equal(t *testing.T) {
  function TestValue_NotEqual (line 150) | func TestValue_NotEqual(t *testing.T) {
  function TestValue_LessThan (line 285) | func TestValue_LessThan(t *testing.T) {
  function TestValue_LessThanOrEqual (line 390) | func TestValue_LessThanOrEqual(t *testing.T) {
  function TestValue_GreaterThan (line 495) | func TestValue_GreaterThan(t *testing.T) {
  function TestValue_GreaterThanOrEqual (line 600) | func TestValue_GreaterThanOrEqual(t *testing.T) {
  function TestValue_Compare (line 705) | func TestValue_Compare(t *testing.T) {

FILE: model/value_literal.go
  function newPtr (line 8) | func newPtr() reflect.Value {
  function NewNullValue (line 13) | func NewNullValue() *Value {
  method IsNull (line 18) | func (v *Value) IsNull() bool {
  method isNull (line 22) | func (v *Value) isNull() bool {
  function NewStringValue (line 32) | func NewStringValue(x string) *Value {
  method IsString (line 39) | func (v *Value) IsString() bool {
  method isString (line 43) | func (v *Value) isString() bool {
  method StringValue (line 48) | func (v *Value) StringValue() (string, error) {
  method StringLen (line 60) | func (v *Value) StringLen() (int, error) {
  method StringIndexRange (line 70) | func (v *Value) StringIndexRange(start, end int) (*Value, error) {
  function NewIntValue (line 104) | func NewIntValue(x int64) *Value {
  method IsInt (line 111) | func (v *Value) IsInt() bool {
  method isInt (line 115) | func (v *Value) isInt() bool {
  method IntValue (line 120) | func (v *Value) IntValue() (int64, error) {
  function NewFloatValue (line 132) | func NewFloatValue(x float64) *Value {
  method IsFloat (line 139) | func (v *Value) IsFloat() bool {
  method isFloat (line 143) | func (v *Value) isFloat() bool {
  method FloatValue (line 148) | func (v *Value) FloatValue() (float64, error) {
  function NewBoolValue (line 160) | func NewBoolValue(x bool) *Value {
  method IsBool (line 167) | func (v *Value) IsBool() bool {
  method isBool (line 171) | func (v *Value) isBool() bool {
  method BoolValue (line 176) | func (v *Value) BoolValue() (bool, error) {

FILE: model/value_literal_test.go
  function TestValue_IsNull (line 9) | func TestValue_IsNull(t *testing.T) {

FILE: model/value_map.go
  function NewMapValue (line 12) | func NewMapValue() *Value {
  method IsMap (line 17) | func (v *Value) IsMap() bool {
  method isStandardMap (line 21) | func (v *Value) isStandardMap() bool {
  method isDencodingMap (line 25) | func (v *Value) isDencodingMap() bool {
  method dencodingMapValue (line 29) | func (v *Value) dencodingMapValue() (*orderedmap.Map, error) {
  method SetMapKey (line 41) | func (v *Value) SetMapKey(key string, value *Value) error {
  method MapCopy (line 62) | func (v *Value) MapCopy() (*Value, error) {
  method MapKeyExists (line 76) | func (v *Value) MapKeyExists(key string) (bool, error) {
  method GetMapKey (line 85) | func (v *Value) GetMapKey(key string) (*Value, error) {
  method DeleteMapKey (line 137) | func (v *Value) DeleteMapKey(key string) error {
  method MapKeys (line 162) | func (v *Value) MapKeys() ([]string, error) {
  method RangeMap (line 190) | func (v *Value) RangeMap(f func(string, *Value) error) error {
  method MapKeyValues (line 210) | func (v *Value) MapKeyValues() ([]KeyValue, error) {
  method MapLen (line 233) | func (v *Value) MapLen() (int, error) {

FILE: model/value_map_test.go
  function TestMap (line 11) | func TestMap(t *testing.T) {

FILE: model/value_math.go
  method Add (line 9) | func (v *Value) Add(other *Value) (*Value, error) {
  method Subtract (line 69) | func (v *Value) Subtract(other *Value) (*Value, error) {
  method Multiply (line 118) | func (v *Value) Multiply(other *Value) (*Value, error) {
  method Divide (line 167) | func (v *Value) Divide(other *Value) (*Value, error) {
  method Modulo (line 216) | func (v *Value) Modulo(other *Value) (*Value, error) {

FILE: model/value_math_test.go
  function TestValue_Add (line 9) | func TestValue_Add(t *testing.T) {
  function TestValue_Subtract (line 40) | func TestValue_Subtract(t *testing.T) {
  function TestValue_Multiply (line 68) | func TestValue_Multiply(t *testing.T) {
  function TestValue_Divide (line 96) | func TestValue_Divide(t *testing.T) {
  function TestValue_Modulo (line 124) | func TestValue_Modulo(t *testing.T) {

FILE: model/value_metadata.go
  method MetadataValue (line 4) | func (v *Value) MetadataValue(key string) (any, bool) {
  method SetMetadataValue (line 13) | func (v *Value) SetMetadataValue(key string, val any) {
  method IsSpread (line 22) | func (v *Value) IsSpread() bool {
  method MarkAsSpread (line 36) | func (v *Value) MarkAsSpread() {
  method IsBranch (line 41) | func (v *Value) IsBranch() bool {
  method MarkAsBranch (line 54) | func (v *Value) MarkAsBranch() {
  method IsIgnore (line 59) | func (v *Value) IsIgnore() bool {
  method MarkAsIgnore (line 72) | func (v *Value) MarkAsIgnore() {

FILE: model/value_metadata_test.go
  function TestValue_IsBranch (line 9) | func TestValue_IsBranch(t *testing.T) {
  function TestValue_IsSpread (line 20) | func TestValue_IsSpread(t *testing.T) {

FILE: model/value_set.go
  method Set (line 9) | func (v *Value) Set(newValue *Value) error {

FILE: model/value_set_test.go
  type setTestCase (line 9) | type setTestCase struct
    method run (line 16) | func (tc setTestCase) run(t *testing.T) {
  function TestValue_Set (line 40) | func TestValue_Set(t *testing.T) {

FILE: model/value_slice.go
  function NewSliceValue (line 9) | func NewSliceValue() *Value {
  method IsSlice (line 19) | func (v *Value) IsSlice() bool {
  method isSlice (line 23) | func (v *Value) isSlice() bool {
  method Append (line 28) | func (v *Value) Append(val *Value) error {
  method SliceLen (line 53) | func (v *Value) SliceLen() (int, error) {
  method GetSliceIndex (line 65) | func (v *Value) GetSliceIndex(i int) (*Value, error) {
  method SetSliceIndex (line 93) | func (v *Value) SetSliceIndex(i int, val *Value) error {
  method RangeSlice (line 109) | func (v *Value) RangeSlice(f func(int, *Value) error) error {
  method SliceIndexRange (line 130) | func (v *Value) SliceIndexRange(start, end int) (*Value, error) {

FILE: model/value_slice_test.go
  function TestSlice (line 9) | func TestSlice(t *testing.T) {

FILE: model/value_test.go
  function TestType_String (line 9) | func TestType_String(t *testing.T) {
  function TestValue_Len (line 28) | func TestValue_Len(t *testing.T) {
  function TestValue_IsScalar (line 55) | func TestValue_IsScalar(t *testing.T) {

FILE: parsing/csv/csv.go
  constant CSV (line 10) | CSV parsing.Format = "csv"
  function init (line 15) | func init() {
  function newCSVWriter (line 20) | func newCSVWriter(options parsing.WriterOptions) (parsing.Writer, error) {
  function valueFromString (line 30) | func valueFromString(s string) (*model.Value, error) {
  function valueToString (line 34) | func valueToString(v *model.Value) (string, error) {

FILE: parsing/csv/csv_test.go
  function TestValueToString (line 8) | func TestValueToString(t *testing.T) {

FILE: parsing/csv/reader.go
  function newCSVReader (line 13) | func newCSVReader(options parsing.ReaderOptions) (parsing.Reader, error) {
  type csvReader (line 23) | type csvReader struct
    method Read (line 28) | func (j *csvReader) Read(data []byte) (*model.Value, error) {

FILE: parsing/csv/reader_test.go
  function TestCsvReader_Read (line 10) | func TestCsvReader_Read(t *testing.T) {

FILE: parsing/csv/writer.go
  type csvWriter (line 10) | type csvWriter struct
    method Write (line 15) | func (j *csvWriter) Write(value *model.Value) ([]byte, error) {

FILE: parsing/csv/writer_test.go
  function TestCsvWriter_Write (line 10) | func TestCsvWriter_Write(t *testing.T) {

FILE: parsing/d/reader.go
  constant Dasel (line 14) | Dasel parsing.Format = "dasel"
  function init (line 19) | func init() {
  function newDaselReader (line 23) | func newDaselReader(options parsing.ReaderOptions) (parsing.Reader, erro...
  type daselReader (line 27) | type daselReader struct
    method Read (line 30) | func (dr *daselReader) Read(in []byte) (*model.Value, error) {

FILE: parsing/format.go
  type Format (line 8) | type Format
    method NewReader (line 11) | func (f Format) NewReader(options ReaderOptions) (Reader, error) {
    method NewWriter (line 20) | func (f Format) NewWriter(options WriterOptions) (Writer, error) {
    method String (line 33) | func (f Format) String() string {
  function RegisteredReaders (line 38) | func RegisteredReaders() []Format {
  function RegisteredWriters (line 47) | func RegisteredWriters() []Format {

FILE: parsing/hcl/hcl.go
  constant HCL (line 9) | HCL parsing.Format = "hcl"
  function init (line 15) | func init() {

FILE: parsing/hcl/reader.go
  function newHCLReader (line 14) | func newHCLReader(options parsing.ReaderOptions) (parsing.Reader, error) {
  type hclReader (line 20) | type hclReader struct
    method Read (line 27) | func (r *hclReader) Read(data []byte) (*model.Value, error) {
    method decodeHCLBody (line 38) | func (r *hclReader) decodeHCLBody(body *hclsyntax.Body) (*model.Value,...
    method decodeHCLBodyBlocks (line 61) | func (r *hclReader) decodeHCLBodyBlocks(body *hclsyntax.Body, res *mod...
    method decodeHCLBlock (line 70) | func (r *hclReader) decodeHCLBlock(block *hclsyntax.Block, res *model....
    method decodeHCLExpr (line 155) | func (r *hclReader) decodeHCLExpr(expr hcl.Expression) (*model.Value, ...
    method decodeCtyValue (line 162) | func (r *hclReader) decodeCtyValue(source cty.Value) (res *model.Value...

FILE: parsing/hcl/reader_test.go
  type readTestCase (line 11) | type readTestCase struct
    method run (line 15) | func (tc readTestCase) run(t *testing.T) {
  function TestHclReader_Read (line 32) | func TestHclReader_Read(t *testing.T) {

FILE: parsing/hcl/writer.go
  function newHCLWriter (line 12) | func newHCLWriter(options parsing.WriterOptions) (parsing.Writer, error) {
  type hclWriter (line 18) | type hclWriter struct
    method Write (line 23) | func (j *hclWriter) Write(value *model.Value) ([]byte, error) {
    method valueToFile (line 36) | func (j *hclWriter) valueToFile(v *model.Value) (*hclwrite.File, error) {
    method addValueToBody (line 48) | func (j *hclWriter) addValueToBody(previousLabels []string, v *model.V...
    method valueToCty (line 118) | func (j *hclWriter) valueToCty(v *model.Value) (cty.Value, error) {
    method valueToBlock (line 177) | func (j *hclWriter) valueToBlock(key string, labels []string, v *model...

FILE: parsing/hcl/writer_test.go
  type readWriteTestCase (line 12) | type readWriteTestCase struct
    method run (line 16) | func (tc readWriteTestCase) run(t *testing.T) {
  function TestHclReader_ReadWrite (line 46) | func TestHclReader_ReadWrite(t *testing.T) {

FILE: parsing/ini/ini.go
  constant INI (line 9) | INI parsing.Format = "ini"
  function init (line 12) | func init() {

FILE: parsing/ini/ini_reader.go
  function init (line 12) | func init() {
  function newINIReader (line 17) | func newINIReader(options parsing.ReaderOptions) (parsing.Reader, error) {
  type iniReader (line 21) | type iniReader struct
    method Read (line 24) | func (j *iniReader) Read(data []byte) (*model.Value, error) {
    method readSection (line 51) | func (j *iniReader) readSection(s *ini.Section) (*model.Value, error) {

FILE: parsing/ini/ini_test.go
  function TestIni (line 11) | func TestIni(t *testing.T) {

FILE: parsing/ini/ini_writer.go
  function newINIWriter (line 14) | func newINIWriter(options parsing.WriterOptions) (parsing.Writer, error) {
  type iniWriter (line 20) | type iniWriter struct
    method Write (line 25) | func (j *iniWriter) Write(value *model.Value) ([]byte, error) {
    method write (line 47) | func (j *iniWriter) write(f *ini.File, path string, value *model.Value...
  function valueToString (line 94) | func valueToString(v *model.Value) (string, error) {

FILE: parsing/json/json.go
  constant JSON (line 10) | JSON parsing.Format = "json"
  constant jsonOpenObject (line 12) | jsonOpenObject  = json.Delim('{')
  constant jsonCloseObject (line 13) | jsonCloseObject = json.Delim('}')
  constant jsonOpenArray (line 14) | jsonOpenArray   = json.Delim('[')
  constant jsonCloseArray (line 15) | jsonCloseArray  = json.Delim(']')
  function init (line 18) | func init() {

FILE: parsing/json/json_reader.go
  function newJSONReader (line 14) | func newJSONReader(options parsing.ReaderOptions) (parsing.Reader, error) {
  type jsonReader (line 18) | type jsonReader struct
    method Read (line 21) | func (j *jsonReader) Read(data []byte) (*model.Value, error) {
    method decodeObject (line 53) | func (j *jsonReader) decodeObject(decoder *json.Decoder) (*model.Value...
    method decodeArray (line 115) | func (j *jsonReader) decodeArray(decoder *json.Decoder) (*model.Value,...
    method decodeToken (line 157) | func (j *jsonReader) decodeToken(decoder *json.Decoder, t json.Token) ...

FILE: parsing/json/json_test.go
  function TestJson (line 11) | func TestJson(t *testing.T) {

FILE: parsing/json/json_writer.go
  function newJSONWriter (line 16) | func newJSONWriter(options parsing.WriterOptions) (parsing.Writer, error) {
  type jsonWriter (line 22) | type jsonWriter struct
    method Write (line 27) | func (j *jsonWriter) Write(value *model.Value) ([]byte, error) {
    method write (line 75) | func (j *jsonWriter) write(w io.Writer, encoder encoderFn, es encoderS...
    method writeMap (line 112) | func (j *jsonWriter) writeMap(w io.Writer, encoder encoderFn, es encod...
    method writeSlice (line 163) | func (j *jsonWriter) writeSlice(w io.Writer, encoder encoderFn, es enc...
  type encoderState (line 52) | type encoderState struct
    method inc (line 57) | func (es encoderState) inc() encoderState {
    method writeIndent (line 62) | func (es encoderState) writeIndent(w io.Writer) error {
  type encoderFn (line 73) | type encoderFn

FILE: parsing/reader.go
  type ReaderOptions (line 7) | type ReaderOptions struct
  function DefaultReaderOptions (line 12) | func DefaultReaderOptions() ReaderOptions {
  type Reader (line 19) | type Reader interface
  type NewReaderFn (line 25) | type NewReaderFn
  function RegisterReader (line 28) | func RegisterReader(format Format, fn NewReaderFn) {

FILE: parsing/toml/toml.go
  constant TOML (line 10) | TOML parsing.Format = "toml"
  function init (line 12) | func init() {

FILE: parsing/toml/toml_reader.go
  function newTOMLReader (line 15) | func newTOMLReader(options parsing.ReaderOptions) (parsing.Reader, error) {
  constant tomlStringStyleKey (line 20) | tomlStringStyleKey = "toml_string_style"
  constant tomlStringStyleMultilineLiteral (line 22) | tomlStringStyleMultilineLiteral = "multiline_literal"
  constant tomlStringStyleMultilineBasic (line 23) | tomlStringStyleMultilineBasic   = "multiline_basic"
  constant tomlStringStyleLiteral (line 24) | tomlStringStyleLiteral          = "literal"
  constant tomlStringStyleBasic (line 25) | tomlStringStyleBasic            = "basic"
  constant tomlTableStyleKey (line 27) | tomlTableStyleKey      = "toml_table_style"
  constant tomlTableStyleStandard (line 28) | tomlTableStyleStandard = "standard"
  constant tomlTableStyleArray (line 29) | tomlTableStyleArray    = "array"
  constant tomlTableStyleInline (line 30) | tomlTableStyleInline   = "inline"
  type tomlReader (line 33) | type tomlReader struct
    method Read (line 36) | func (j *tomlReader) Read(data []byte) (*model.Value, error) {
    method readNode (line 110) | func (j *tomlReader) readNode(p *unstable.Parser, n *unstable.Node) (s...
    method parseKeyValueNode (line 172) | func (j *tomlReader) parseKeyValueNode(p *unstable.Parser, n *unstable...
    method readInlineTable (line 327) | func (j *tomlReader) readInlineTable(p *unstable.Parser, n *unstable.N...
    method readArrayValue (line 362) | func (j *tomlReader) readArrayValue(p *unstable.Parser, n *unstable.No...
  function extractKeyFromTableNode (line 201) | func extractKeyFromTableNode(p *unstable.Parser, n *unstable.Node) ([]st...
  function ensureMapAt (line 226) | func ensureMapAt(root *model.Value, path []string) (*model.Value, error) {
  function ensureSliceAt (line 267) | func ensureSliceAt(root *model.Value, path []string) (*model.Value, erro...
  function setDottedKey (line 301) | func setDottedKey(root, active *model.Value, parts []string, val *model....

FILE: parsing/toml/toml_reader_test.go
  function tomlReaderTest (line 13) | func tomlReaderTest(data []byte, exp func() *model.Value) func(*testing....
  function TestTomlReader_Read (line 41) | func TestTomlReader_Read(t *testing.T) {
  function TestTomlReader_MoreCases (line 158) | func TestTomlReader_MoreCases(t *testing.T) {
  function TestTomlReader_QuotedKeys (line 285) | func TestTomlReader_QuotedKeys(t *testing.T) {
  function TestTomlReader_ComplexFile (line 312) | func TestTomlReader_ComplexFile(t *testing.T) {
  function TestTomlReader_EdgeCases (line 383) | func TestTomlReader_EdgeCases(t *testing.T) {
  function TestTomlReader_TimeStrings (line 517) | func TestTomlReader_TimeStrings(t *testing.T) {

FILE: parsing/toml/toml_writer.go
  function newTOMLWriter (line 17) | func newTOMLWriter(options parsing.WriterOptions) (parsing.Writer, error) {
  type tomlWriter (line 22) | type tomlWriter struct
    method Write (line 30) | func (j *tomlWriter) Write(value *model.Value) ([]byte, error) {
  function buildGoValueForMap (line 79) | func buildGoValueForMap(v *model.Value) (interface{}, error) {
  function goTypeAndValue (line 139) | func goTypeAndValue(v *model.Value) (reflect.Type, reflect.Value, error) {

FILE: parsing/toml/toml_writer_test.go
  function TestTomlWriter_RoundTripSimple (line 12) | func TestTomlWriter_RoundTripSimple(t *testing.T) {
  function TestTomlWriter_OrderPreserved (line 59) | func TestTomlWriter_OrderPreserved(t *testing.T) {
  function TestTomlWriter_ArrayOfTables_RoundTrip (line 93) | func TestTomlWriter_ArrayOfTables_RoundTrip(t *testing.T) {
  function TestTomlWriter_MoreCases (line 140) | func TestTomlWriter_MoreCases(t *testing.T) {
  function TestTomlWriter_StrictOutput (line 244) | func TestTomlWriter_StrictOutput(t *testing.T) {

FILE: parsing/writer.go
  type WriterOptions (line 12) | type WriterOptions struct
  function DefaultWriterOptions (line 19) | func DefaultWriterOptions() WriterOptions {
  type Writer (line 28) | type Writer interface
  type NewWriterFn (line 34) | type NewWriterFn
  function RegisterWriter (line 37) | func RegisterWriter(format Format, fn NewWriterFn) {
  type DocumentSeparator (line 42) | type DocumentSeparator interface
  function MultiDocumentWriter (line 48) | func MultiDocumentWriter(w Writer) Writer {
  type multiDocumentWriter (line 52) | type multiDocumentWriter struct
    method Write (line 57) | func (w *multiDocumentWriter) Write(value *model.Value) ([]byte, error) {

FILE: parsing/xml/reader.go
  constant maxCommentLength (line 18) | maxCommentLength = 10_000
  constant maxTotalComments (line 19) | maxTotalComments = 1_000
  constant maxXMLSize (line 20) | maxXMLSize       = 10_000_000
  function newXMLReader (line 23) | func newXMLReader(options parsing.ReaderOptions) (parsing.Reader, error) {
  type xmlReader (line 29) | type xmlReader struct
    method Read (line 34) | func (j *xmlReader) Read(data []byte) (*model.Value, error) {
    method parseElement (line 167) | func (j *xmlReader) parseElement(decoder *xml.Decoder, element xml.Sta...
  method toStructuredModel (line 58) | func (e *xmlElement) toStructuredModel() (*model.Value, error) {
  method toFriendlyModel (line 98) | func (e *xmlElement) toFriendlyModel() (*model.Value, error) {

FILE: parsing/xml/reader_test.go
  function TestXmlReader_Read (line 11) | func TestXmlReader_Read(t *testing.T) {

FILE: parsing/xml/structured_comment_test.go
  function newTestReaderWriter (line 12) | func newTestReaderWriter(t *testing.T) (parsing.Reader, parsing.Writer) {
  function assertRoundTrip (line 26) | func assertRoundTrip(t *testing.T, r parsing.Reader, w parsing.Writer, i...
  function TestXmlReader_StructuredModeWithComments (line 47) | func TestXmlReader_StructuredModeWithComments(t *testing.T) {
  function TestXmlRoundTrip_CommentPreservation (line 93) | func TestXmlRoundTrip_CommentPreservation(t *testing.T) {
  function TestXmlRoundTrip_EdgeCases (line 157) | func TestXmlRoundTrip_EdgeCases(t *testing.T) {
  function TestXmlRoundTrip_SpecialCommentContent (line 209) | func TestXmlRoundTrip_SpecialCommentContent(t *testing.T) {
  function TestXmlReader_SecurityLimits (line 236) | func TestXmlReader_SecurityLimits(t *testing.T) {
  function TestXmlRoundTrip_ProcessingInstructionReset (line 298) | func TestXmlRoundTrip_ProcessingInstructionReset(t *testing.T) {
  function TestXmlRoundTrip_ProcessingInstructions (line 384) | func TestXmlRoundTrip_ProcessingInstructions(t *testing.T) {

FILE: parsing/xml/writer.go
  function newXMLWriter (line 13) | func newXMLWriter(options parsing.WriterOptions) (parsing.Writer, error) {
  type xmlWriter (line 19) | type xmlWriter struct
    method Write (line 24) | func (j *xmlWriter) Write(value *model.Value) ([]byte, error) {
    method toElement (line 55) | func (j *xmlWriter) toElement(key string, value *model.Value) (*xmlEle...
  function valueToString (line 156) | func valueToString(v *model.Value) (string, error) {
  function indentString (line 192) | func indentString(depth int) string {
  method MarshalXML (line 196) | func (e *xmlElement) MarshalXML(enc *xml.Encoder, start xml.StartElement...

FILE: parsing/xml/writer_internal_test.go
  function TestXmlWriter_CommentValidation (line 12) | func TestXmlWriter_CommentValidation(t *testing.T) {
  function Test_valueToString (line 74) | func Test_valueToString(t *testing.T) {

FILE: parsing/xml/writer_test.go
  function TestXmlReader_Write (line 11) | func TestXmlReader_Write(t *testing.T) {

FILE: parsing/xml/xml.go
  constant XML (line 9) | XML parsing.Format = "xml"
  function init (line 15) | func init() {
  type xmlAttr (line 20) | type xmlAttr struct
  type xmlProcessingInstruction (line 25) | type xmlProcessingInstruction struct
  type xmlComment (line 30) | type xmlComment struct
  type xmlElement (line 34) | type xmlElement struct

FILE: parsing/yaml/yaml.go
  constant YAML (line 10) | YAML parsing.Format = "yaml"
  function init (line 12) | func init() {
  type yamlValue (line 17) | type yamlValue struct

FILE: parsing/yaml/yaml_reader.go
  function newYAMLReader (line 17) | func newYAMLReader(options parsing.ReaderOptions) (parsing.Reader, error) {
  type yamlReader (line 24) | type yamlReader struct
    method Read (line 39) | func (j *yamlReader) Read(data []byte) (*model.Value, error) {
  constant maxExpansionDepth (line 35) | maxExpansionDepth = 32
  constant maxExpansionBudget (line 36) | maxExpansionBudget = 1000
  method UnmarshalYAML (line 88) | func (yv *yamlValue) UnmarshalYAML(value *yaml.Node) error {

FILE: parsing/yaml/yaml_test.go
  type testCase (line 16) | type testCase struct
    method run (line 21) | func (tc testCase) run(t *testing.T) {
  type rwTestCase (line 34) | type rwTestCase struct
    method run (line 39) | func (tc rwTestCase) run(t *testing.T) {
  function TestYamlValue_UnmarshalYAML (line 65) | func TestYamlValue_UnmarshalYAML(t *testing.T) {

FILE: parsing/yaml/yaml_writer.go
  function newYAMLWriter (line 13) | func newYAMLWriter(options parsing.WriterOptions) (parsing.Writer, error) {
  type yamlWriter (line 17) | type yamlWriter struct
    method Separator (line 19) | func (j *yamlWriter) Separator() []byte {
    method Write (line 24) | func (j *yamlWriter) Write(value *model.Value) ([]byte, error) {
  method ToNode (line 33) | func (yv *yamlValue) ToNode() (*yaml.Node, error) {
  method MarshalYAML (line 123) | func (yv *yamlValue) MarshalYAML() (any, error) {

FILE: selector/ast/ast.go
  type Program (line 3) | type Program struct
  type Statement (line 7) | type Statement struct
  type Expressions (line 11) | type Expressions
  type Expr (line 13) | type Expr interface
  function IsType (line 17) | func IsType[T Expr](e Expr) bool {
  function AsType (line 22) | func AsType[T Expr](e Expr) (T, bool) {
  function LastAsType (line 27) | func LastAsType[T Expr](e Expr) (T, bool) {
  function Last (line 31) | func Last(e Expr) Expr {
  function RemoveLast (line 38) | func RemoveLast(e Expr) Expr {

FILE: selector/ast/ast_test.go
  function TestExpr_expr (line 8) | func TestExpr_expr(t *testing.T) {

FILE: selector/ast/expression_complex.go
  type BinaryExpr (line 5) | type BinaryExpr struct
    method expr (line 11) | func (BinaryExpr) expr() {}
  type UnaryExpr (line 13) | type UnaryExpr struct
    method expr (line 18) | func (UnaryExpr) expr() {}
  type CallExpr (line 20) | type CallExpr struct
    method expr (line 25) | func (CallExpr) expr() {}
  type ChainedExpr (line 27) | type ChainedExpr struct
    method expr (line 43) | func (ChainedExpr) expr() {}
  function ChainExprs (line 31) | func ChainExprs(exprs ...Expr) Expr {
  type SpreadExpr (line 45) | type SpreadExpr struct
    method expr (line 47) | func (SpreadExpr) expr() {}
  type RangeExpr (line 49) | type RangeExpr struct
    method expr (line 54) | func (RangeExpr) expr() {}
  type IndexExpr (line 56) | type IndexExpr struct
    method expr (line 60) | func (IndexExpr) expr() {}
  type ArrayExpr (line 62) | type ArrayExpr struct
    method expr (line 66) | func (ArrayExpr) expr() {}
  type PropertyExpr (line 68) | type PropertyExpr struct
    method expr (line 75) | func (PropertyExpr) expr() {}
  type KeyValue (line 77) | type KeyValue struct
  type ObjectExpr (line 82) | type ObjectExpr struct
    method expr (line 86) | func (ObjectExpr) expr() {}
  type MapExpr (line 88) | type MapExpr struct
    method expr (line 92) | func (MapExpr) expr() {}
  type EachExpr (line 94) | type EachExpr struct
    method expr (line 98) | func (EachExpr) expr() {}
  type FilterExpr (line 100) | type FilterExpr struct
    method expr (line 104) | func (FilterExpr) expr() {}
  type SearchExpr (line 106) | type SearchExpr struct
    method expr (line 110) | func (SearchExpr) expr() {}
  type RecursiveDescentExpr (line 112) | type RecursiveDescentExpr struct
    method expr (line 117) | func (RecursiveDescentExpr) expr() {}
  type SortByExpr (line 119) | type SortByExpr struct
    method expr (line 124) | func (SortByExpr) expr() {}
  type VariableExpr (line 126) | type VariableExpr struct
    method expr (line 130) | func (VariableExpr) expr() {}
  type GroupExpr (line 132) | type GroupExpr struct
    method expr (line 136) | func (GroupExpr) expr() {}
  type ConditionalExpr (line 138) | type ConditionalExpr struct
    method expr (line 144) | func (ConditionalExpr) expr() {}
  type BranchExpr (line 146) | type BranchExpr struct
    method expr (line 150) | func (BranchExpr) expr() {}
  function BranchExprs (line 152) | func BranchExprs(exprs ...Expr) Expr {
  type AssignExpr (line 158) | type AssignExpr struct
    method expr (line 163) | func (AssignExpr) expr() {}

FILE: selector/ast/expression_literal.go
  type NumberFloatExpr (line 5) | type NumberFloatExpr struct
    method expr (line 9) | func (NumberFloatExpr) expr() {}
  type NumberIntExpr (line 11) | type NumberIntExpr struct
    method expr (line 15) | func (NumberIntExpr) expr() {}
  type StringExpr (line 17) | type StringExpr struct
    method expr (line 21) | func (StringExpr) expr() {}
  type BoolExpr (line 23) | type BoolExpr struct
    method expr (line 27) | func (BoolExpr) expr() {}
  type RegexExpr (line 29) | type RegexExpr struct
    method expr (line 33) | func (RegexExpr) expr() {}
  type NullExpr (line 35) | type NullExpr struct
    method expr (line 37) | func (NullExpr) expr() {}

FILE: selector/lexer/token.go
  type TokenKind (line 8) | type TokenKind
  function TokenKinds (line 10) | func TokenKinds(tk ...TokenKind) []TokenKind {
  constant EOF (line 15) | EOF TokenKind = iota
  constant Symbol (line 16) | Symbol
  constant Comma (line 17) | Comma
  constant Colon (line 18) | Colon
  constant OpenBracket (line 19) | OpenBracket
  constant CloseBracket (line 20) | CloseBracket
  constant OpenCurly (line 21) | OpenCurly
  constant CloseCurly (line 22) | CloseCurly
  constant OpenParen (line 23) | OpenParen
  constant CloseParen (line 24) | CloseParen
  constant Equal (line 25) | Equal
  constant Equals (line 26) | Equals
  constant NotEqual (line 27) | NotEqual
  constant And (line 28) | And
  constant Or (line 29) | Or
  constant Like (line 30) | Like
  constant NotLike (line 31) | NotLike
  constant String (line 32) | String
  constant Number (line 33) | Number
  constant Bool (line 34) | Bool
  constant Plus (line 35) | Plus
  constant Increment (line 36) | Increment
  constant IncrementBy (line 37) | IncrementBy
  constant Dash (line 38) | Dash
  constant Decrement (line 39) | Decrement
  constant DecrementBy (line 40) | DecrementBy
  constant Star (line 41) | Star
  constant Slash (line 42) | Slash
  constant Percent (line 43) | Percent
  constant Dot (line 44) | Dot
  constant Spread (line 45) | Spread
  constant RecursiveDescent (line 46) | RecursiveDescent
  constant Dollar (line 47) | Dollar
  constant Variable (line 48) | Variable
  constant GreaterThan (line 49) | GreaterThan
  constant GreaterThanOrEqual (line 50) | GreaterThanOrEqual
  constant LessThan (line 51) | LessThan
  constant LessThanOrEqual (line 52) | LessThanOrEqual
  constant Exclamation (line 53) | Exclamation
  constant Null (line 54) | Null
  constant If (line 55) | If
  constant Else (line 56) | Else
  constant ElseIf (line 57) | ElseIf
  constant Branch (line 58) | Branch
  constant Map (line 59) | Map
  constant Each (line 60) | Each
  constant Filter (line 61) | Filter
  constant Search (line 62) | Search
  constant RegexPattern (line 63) | RegexPattern
  constant SortBy (line 64) | SortBy
  constant Asc (line 65) | Asc
  constant Desc (line 66) | Desc
  constant QuestionMark (line 67) | QuestionMark
  constant DoubleQuestionMark (line 68) | DoubleQuestionMark
  constant Semicolon (line 69) | Semicolon
  type Tokens (line 72) | type Tokens
    method Split (line 74) | func (tt Tokens) Split(kind TokenKind) []Tokens {
  type Token (line 93) | type Token struct
    method IsKind (line 109) | func (t Token) IsKind(kind ...TokenKind) bool {
  function NewToken (line 100) | func NewToken(kind TokenKind, value string, pos int, len int) Token {
  type UnexpectedTokenError (line 113) | type UnexpectedTokenError struct
    method Error (line 118) | func (e *UnexpectedTokenError) Error() string {
  type UnexpectedEOFError (line 122) | type UnexpectedEOFError struct
    method Error (line 126) | func (e *UnexpectedEOFError) Error() string {

FILE: selector/lexer/tokenize.go
  type Tokenizer (line 10) | type Tokenizer struct
    method Tokenize (line 24) | func (p *Tokenizer) Tokenize() (Tokens, error) {
    method peekRuneEqual (line 39) | func (p *Tokenizer) peekRuneEqual(i int, to rune) bool {
    method peekRuneMatches (line 46) | func (p *Tokenizer) peekRuneMatches(i int, fn func(rune) bool) bool {
    method parseCurRune (line 53) | func (p *Tokenizer) parseCurRune() (Token, error) {
    method Next (line 328) | func (p *Tokenizer) Next() (Token, error) {
  function NewTokenizer (line 16) | func NewTokenizer(src string) *Tokenizer {

FILE: selector/lexer/tokenize_test.go
  type testCase (line 10) | type testCase struct
    method run (line 15) | func (tc testCase) run(t *testing.T) {
  type errTestCase (line 32) | type errTestCase struct
    method run (line 37) | func (tc errTestCase) run(t *testing.T) {
  function matchUnexpectedError (line 49) | func matchUnexpectedError(r rune, p int) func(error) bool {
  function matchUnexpectedEOFError (line 60) | func matchUnexpectedEOFError(p int) func(error) bool {
  function TestTokenizer_Parse (line 71) | func TestTokenizer_Parse(t *testing.T) {

FILE: selector/parser.go
  function Parse (line 9) | func Parse(selector string) (ast.Expr, error) {

FILE: selector/parser/denotations.go
  type bindingPower (line 31) | type bindingPower
  constant bpDefault (line 34) | bpDefault bindingPower = iota
  constant bpAssignment (line 35) | bpAssignment
  constant bpLogical (line 36) | bpLogical
  constant bpEarlyLogical (line 37) | bpEarlyLogical
  constant bpRelational (line 38) | bpRelational
  constant bpAdditive (line 39) | bpAdditive
  constant bpMultiplicative (line 40) | bpMultiplicative
  constant bpUnary (line 41) | bpUnary
  constant bpCall (line 42) | bpCall
  constant bpProperty (line 43) | bpProperty
  constant bpLiteral (line 44) | bpLiteral
  function getTokenBindingPower (line 85) | func getTokenBindingPower(t lexer.TokenKind) bindingPower {

FILE: selector/parser/error.go
  type PositionalError (line 9) | type PositionalError struct
    method Error (line 14) | func (e *PositionalError) Error() string {
  type UnexpectedTokenError (line 18) | type UnexpectedTokenError struct
    method Error (line 22) | func (e *UnexpectedTokenError) Error() string {

FILE: selector/parser/parse_array.go
  function parseArray (line 8) | func parseArray(p *Parser) (ast.Expr, error) {
  function parseIndexSquareBrackets (line 39) | func parseIndexSquareBrackets(p *Parser, expectIndex bool) (ast.Expr, er...

FILE: selector/parser/parse_branch.go
  function parseBranch (line 8) | func parseBranch(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_each.go
  function parseEach (line 8) | func parseEach(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_filter.go
  function parseFilter (line 8) | func parseFilter(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_func.go
  function parseFunc (line 8) | func parseFunc(p *Parser) (ast.Expr, error) {
  function parseArgs (line 29) | func parseArgs(p *Parser) (ast.Expressions, error) {

FILE: selector/parser/parse_group.go
  function parseGroup (line 8) | func parseGroup(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_if.go
  function parseIfBody (line 8) | func parseIfBody(p *Parser) (ast.Expr, error) {
  function parseIfCondition (line 12) | func parseIfCondition(p *Parser) (ast.Expr, error) {
  function parseIf (line 16) | func parseIf(p *Parser) (ast.Expr, error) {
  method parseExpressionsFromTo (line 78) | func (p *Parser) parseExpressionsFromTo(

FILE: selector/parser/parse_literal.go
  function parseStringLiteral (line 13) | func parseStringLiteral(p *Parser) (ast.Expr, error) {
  function parseBoolLiteral (line 21) | func parseBoolLiteral(p *Parser) (ast.Expr, error) {
  function parseSpread (line 29) | func parseSpread(p *Parser) (ast.Expr, error) {
  function parseNumberLiteral (line 34) | func parseNumberLiteral(p *Parser) (ast.Expr, error) {
  function parseRegexPattern (line 84) | func parseRegexPattern(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_map.go
  function parseMap (line 8) | func parseMap(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_object.go
  function parseObject (line 10) | func parseObject(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_recursive_descent.go
  function parseRecursiveDescent (line 8) | func parseRecursiveDescent(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_search.go
  function parseSearch (line 8) | func parseSearch(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_sort_by.go
  function parseSortBy (line 8) | func parseSortBy(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parse_symbol.go
  function parseFollowingSymbol (line 11) | func parseFollowingSymbol(p *Parser, prev ast.Expr) (ast.Expr, error) {
  function parseSymbol (line 67) | func parseSymbol(p *Parser, withFollowing bool, allowFunc bool) (ast.Exp...

FILE: selector/parser/parse_variable.go
  function parseVariable (line 7) | func parseVariable(p *Parser) (ast.Expr, error) {

FILE: selector/parser/parser.go
  type Parser (line 10) | type Parser struct
    method parseExpressionsAsSlice (line 21) | func (p *Parser) parseExpressionsAsSlice(
    method parseExpressions (line 64) | func (p *Parser) parseExpressions(
    method Parse (line 83) | func (p *Parser) Parse() (ast.Expr, error) {
    method parseExpression (line 87) | func (p *Parser) parseExpression(bp bindingPower) (left ast.Expr, err ...
    method hasToken (line 203) | func (p *Parser) hasToken() bool {
    method hasTokenN (line 207) | func (p *Parser) hasTokenN(n int) bool {
    method current (line 211) | func (p *Parser) current() lexer.Token {
    method advance (line 218) | func (p *Parser) advance() lexer.Token {
    method advanceN (line 222) | func (p *Parser) advanceN(n int) lexer.Token {
    method peek (line 227) | func (p *Parser) peek() lexer.Token {
    method peekN (line 231) | func (p *Parser) peekN(n int) lexer.Token {
    method expect (line 238) | func (p *Parser) expect(kind ...lexer.TokenKind) error {
    method expectN (line 248) | func (p *Parser) expectN(n int, kind ...lexer.TokenKind) error {
  function NewParser (line 15) | func NewParser(tokens lexer.Tokens) *Parser {

FILE: selector/parser/parser_binary.go
  function parseBinary (line 7) | func parseBinary(p *Parser, left ast.Expr) (ast.Expr, error) {

FILE: selector/parser/parser_test.go
  type happyTestCase (line 12) | type happyTestCase struct
    method run (line 17) | func (tc happyTestCase) run(t *testing.T) {
  function TestParser_Parse_HappyPath (line 31) | func TestParser_Parse_HappyPath(t *testing.T) {
Condensed preview — 209 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (583K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 602,
    "preview": "# These are supported funding model platforms\n\ngithub: TomWright # Replace with up to 4 GitHub Sponsors-enabled username"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 827,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: \"bug\"\nassignees: \"\"\n---\n\n**Discussion?*"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 605,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"\"\nlabels: \"enhancement\"\nassignees: \"\"\n---\n\n**I"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 504,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/build-dev.yaml",
    "chars": 2212,
    "preview": "on:\n  push:\n    branches:\n      - master\n      - main\nname: Build Dev\njobs:\n  publish:\n    strategy:\n      matrix:\n     "
  },
  {
    "path": ".github/workflows/build-test.yaml",
    "chars": 2734,
    "preview": "on:\n  push:\n    branches-ignore:\n      - master\n      - main\n  pull_request:\n    branches:\n      - master\n      - main\nn"
  },
  {
    "path": ".github/workflows/build.yaml",
    "chars": 3245,
    "preview": "on:\n  push:\n    tags:\n      - 'v*.*.*'\n\nname: Build\njobs:\n  publish:\n    strategy:\n      matrix:\n        os:\n          -"
  },
  {
    "path": ".github/workflows/bump-homebrew.yaml",
    "chars": 630,
    "preview": "on:\n  workflow_run:\n    workflows: [\"Build\"]\n    types:\n      - completed\nname: Bump homebrew\njobs:\n  publish:\n    name:"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 2350,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/container.yaml",
    "chars": 3849,
    "preview": "on: [push, pull_request]\n\nenv:\n  GOLANG_VERSION: 1\n  IMAGE_NAME: ghcr.io/tomwright/dasel\n\nname: Container build, test an"
  },
  {
    "path": ".github/workflows/golangci-lint.yaml",
    "chars": 443,
    "preview": "name: golangci-lint\non:\n  push:\n    branches:\n      - main\n      - master\n  pull_request:\n\npermissions:\n  contents: read"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 825,
    "preview": "on: [push, pull_request]\nname: Test\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Go\n       "
  },
  {
    "path": ".gitignore",
    "chars": 12,
    "preview": ".idea/\ndasel"
  },
  {
    "path": ".golangci.yaml",
    "chars": 60,
    "preview": "version: \"2\"\nlinters:\n  default: standard\nrun:\n  timeout: 2m"
  },
  {
    "path": ".pre-commit-hooks.yaml",
    "chars": 698,
    "preview": "- id: dasel-validate-docker\n  name: Validate JSON, YAML, XML, TOML files\n  description: Validate JSON files\n  language: "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 24308,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3355,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1933,
    "preview": "# Contributing to Dasel\n\nThank you for considering contributing to Dasel! Contributions of all kinds are welcome — wheth"
  },
  {
    "path": "Dockerfile",
    "chars": 465,
    "preview": "ARG GOLANG_VERSION=1.25.0\nARG TARGET_BASE_IMAGE=debian:bookworm-slim\nFROM golang:${GOLANG_VERSION} AS builder\n\nARG MAJOR"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2020 Tom Wright\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 4289,
    "preview": "[![Gitbook](https://badges.aleen42.com/src/gitbook_1.svg)](https://daseldocs.tomwright.me)\n[![Go Report Card](https://go"
  },
  {
    "path": "SECURITY.md",
    "chars": 1329,
    "preview": "# Security Policy\n\n## Supported Versions\n\nOnly the latest major version of Dasel is currently supported with security up"
  },
  {
    "path": "api.go",
    "chars": 1874,
    "preview": "// Package dasel contains everything you'll need to use dasel from a go application.\npackage dasel\n\nimport (\n\t\"context\"\n"
  },
  {
    "path": "api_example_test.go",
    "chars": 820,
    "preview": "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/executio"
  },
  {
    "path": "api_test.go",
    "chars": 2214,
    "preview": "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 modify"
  },
  {
    "path": "codecov.yaml",
    "chars": 412,
    "preview": "comment: no # do not comment PR with the result\n\ncoverage:\n  range: 50..90 # coverage lower than 50 is red, higher than "
  },
  {
    "path": "execution/README.md",
    "chars": 123,
    "preview": "# Execution\n\nThe execution package accepts a `model.Value`, parses a selector and executes the resulting AST on the valu"
  },
  {
    "path": "execution/context.go",
    "chars": 1040,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype ctxKey string\n\nconst (\n\texecutorIDCtxKey    ctxKey = \"executorID\"\n"
  },
  {
    "path": "execution/execute.go",
    "chars": 5154,
    "preview": "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/da"
  },
  {
    "path": "execution/execute_array.go",
    "chars": 2614,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_array_test.go",
    "chars": 3017,
    "preview": "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\ti"
  },
  {
    "path": "execution/execute_assign.go",
    "chars": 377,
    "preview": "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/sel"
  },
  {
    "path": "execution/execute_assign_test.go",
    "chars": 2302,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/"
  },
  {
    "path": "execution/execute_binary.go",
    "chars": 8079,
    "preview": "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/d"
  },
  {
    "path": "execution/execute_binary_test.go",
    "chars": 7585,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/mode"
  },
  {
    "path": "execution/execute_branch.go",
    "chars": 1378,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_branch_test.go",
    "chars": 4075,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwright/dasel/v3/"
  },
  {
    "path": "execution/execute_conditional.go",
    "chars": 1054,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_conditional_test.go",
    "chars": 1194,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestConditional(t *testing.T"
  },
  {
    "path": "execution/execute_each.go",
    "chars": 710,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_each_test.go",
    "chars": 589,
    "preview": "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."
  },
  {
    "path": "execution/execute_filter.go",
    "chars": 969,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_filter_test.go",
    "chars": 2407,
    "preview": "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\t"
  },
  {
    "path": "execution/execute_func.go",
    "chars": 1745,
    "preview": "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/t"
  },
  {
    "path": "execution/execute_func_test.go",
    "chars": 1031,
    "preview": "package execution_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/execution\"\n\t\"github.com/tomwrigh"
  },
  {
    "path": "execution/execute_literal.go",
    "chars": 1202,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/selector/a"
  },
  {
    "path": "execution/execute_literal_test.go",
    "chars": 3078,
    "preview": "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"
  },
  {
    "path": "execution/execute_map.go",
    "chars": 852,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_map_test.go",
    "chars": 1214,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/mode"
  },
  {
    "path": "execution/execute_object.go",
    "chars": 2589,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_object_test.go",
    "chars": 2794,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/mode"
  },
  {
    "path": "execution/execute_recursive_descent.go",
    "chars": 2600,
    "preview": "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/"
  },
  {
    "path": "execution/execute_search.go",
    "chars": 2349,
    "preview": "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/"
  },
  {
    "path": "execution/execute_sort_by.go",
    "chars": 1434,
    "preview": "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/d"
  },
  {
    "path": "execution/execute_sort_by_test.go",
    "chars": 4464,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncSortBy(t *testing.T)"
  },
  {
    "path": "execution/execute_spread.go",
    "chars": 1696,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc spreadExprExecutor() (expr"
  },
  {
    "path": "execution/execute_spread_test.go",
    "chars": 687,
    "preview": "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\t"
  },
  {
    "path": "execution/execute_test.go",
    "chars": 3429,
    "preview": "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/"
  },
  {
    "path": "execution/execute_unary.go",
    "chars": 868,
    "preview": "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/se"
  },
  {
    "path": "execution/execute_unary_test.go",
    "chars": 1709,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/mode"
  },
  {
    "path": "execution/func.go",
    "chars": 4392,
    "preview": "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"
  },
  {
    "path": "execution/func_add.go",
    "chars": 995,
    "preview": "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 a"
  },
  {
    "path": "execution/func_add_test.go",
    "chars": 1339,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/mode"
  },
  {
    "path": "execution/func_base64.go",
    "chars": 962,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncBase64Encode"
  },
  {
    "path": "execution/func_contains.go",
    "chars": 899,
    "preview": "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 th"
  },
  {
    "path": "execution/func_contains_test.go",
    "chars": 388,
    "preview": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n"
  },
  {
    "path": "execution/func_get.go",
    "chars": 698,
    "preview": "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 return"
  },
  {
    "path": "execution/func_get_test.go",
    "chars": 682,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/pa"
  },
  {
    "path": "execution/func_has.go",
    "chars": 1168,
    "preview": "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 tr"
  },
  {
    "path": "execution/func_has_test.go",
    "chars": 854,
    "preview": "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"
  },
  {
    "path": "execution/func_ignore.go",
    "chars": 372,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncIgnore is a function that ignore"
  },
  {
    "path": "execution/func_join.go",
    "chars": 1600,
    "preview": "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 fun"
  },
  {
    "path": "execution/func_join_test.go",
    "chars": 514,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/pa"
  },
  {
    "path": "execution/func_keys.go",
    "chars": 971,
    "preview": "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"
  },
  {
    "path": "execution/func_keys_test.go",
    "chars": 750,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/pa"
  },
  {
    "path": "execution/func_len.go",
    "chars": 425,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncLen is a function that returns t"
  },
  {
    "path": "execution/func_len_test.go",
    "chars": 447,
    "preview": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n"
  },
  {
    "path": "execution/func_max.go",
    "chars": 610,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncMax is a function that returns t"
  },
  {
    "path": "execution/func_max_test.go",
    "chars": 387,
    "preview": "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"
  },
  {
    "path": "execution/func_merge.go",
    "chars": 1219,
    "preview": "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"
  },
  {
    "path": "execution/func_merge_test.go",
    "chars": 1444,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncMerge(t *testing.T) "
  },
  {
    "path": "execution/func_min.go",
    "chars": 607,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncMin is a function that returns t"
  },
  {
    "path": "execution/func_min_test.go",
    "chars": 383,
    "preview": "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"
  },
  {
    "path": "execution/func_parse.go",
    "chars": 824,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)"
  },
  {
    "path": "execution/func_parse_test.go",
    "chars": 285,
    "preview": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/json\"\n"
  },
  {
    "path": "execution/func_readfile.go",
    "chars": 723,
    "preview": "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 read"
  },
  {
    "path": "execution/func_replace.go",
    "chars": 841,
    "preview": "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 functio"
  },
  {
    "path": "execution/func_replace_test.go",
    "chars": 851,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncReplace(t *testing.T"
  },
  {
    "path": "execution/func_reverse.go",
    "chars": 564,
    "preview": "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 th"
  },
  {
    "path": "execution/func_reverse_test.go",
    "chars": 691,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncReverse(t *testing.T"
  },
  {
    "path": "execution/func_sum.go",
    "chars": 1429,
    "preview": "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 r"
  },
  {
    "path": "execution/func_sum_test.go",
    "chars": 633,
    "preview": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncSum(t *testing.T) {\n\t"
  },
  {
    "path": "execution/func_to_float.go",
    "chars": 1075,
    "preview": "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 "
  },
  {
    "path": "execution/func_to_float_test.go",
    "chars": 480,
    "preview": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncToFloat(t *testing.T)"
  },
  {
    "path": "execution/func_to_int.go",
    "chars": 1061,
    "preview": "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 fu"
  },
  {
    "path": "execution/func_to_int_test.go",
    "chars": 456,
    "preview": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncToInt(t *testing.T) {"
  },
  {
    "path": "execution/func_to_string.go",
    "chars": 1085,
    "preview": "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 t"
  },
  {
    "path": "execution/func_to_string_test.go",
    "chars": 504,
    "preview": "package execution_test\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestFuncToString(t *testing.T"
  },
  {
    "path": "execution/func_type_of.go",
    "chars": 383,
    "preview": "package execution\n\nimport (\n\t\"context\"\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\n// FuncTypeOf is a function that return"
  },
  {
    "path": "execution/func_type_of_test.go",
    "chars": 778,
    "preview": "package execution_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\nfunc TestFuncTypeOf(t *testing.T)"
  },
  {
    "path": "execution/options.go",
    "chars": 1321,
    "preview": "package execution\n\nimport \"github.com/tomwright/dasel/v3/model\"\n\n// ExecuteOptionFn is a function that can be used to se"
  },
  {
    "path": "go.mod",
    "chars": 1964,
    "preview": "module github.com/tomwright/dasel/v3\n\ngo 1.25\n\nrequire (\n\tgithub.com/alecthomas/kong v1.14.0\n\tgithub.com/charmbracelet/b"
  },
  {
    "path": "go.sum",
    "chars": 9236,
    "preview": "github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=\ngithub.com/MakeNowJust/heredoc v1."
  },
  {
    "path": "internal/cli/command.go",
    "chars": 1800,
    "preview": "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"
  },
  {
    "path": "internal/cli/command_test.go",
    "chars": 5636,
    "preview": "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\""
  },
  {
    "path": "internal/cli/config.go",
    "chars": 1110,
    "preview": "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 c"
  },
  {
    "path": "internal/cli/generic_test.go",
    "chars": 6098,
    "preview": "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/"
  },
  {
    "path": "internal/cli/interactive.go",
    "chars": 3230,
    "preview": "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"
  },
  {
    "path": "internal/cli/interactive_tea.go",
    "chars": 5666,
    "preview": "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/"
  },
  {
    "path": "internal/cli/interactive_tea_input.go",
    "chars": 1047,
    "preview": "package cli\n\nimport (\n\t\"github.com/charmbracelet/bubbles/textarea\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n)\n\ntype int"
  },
  {
    "path": "internal/cli/interactive_tea_output.go",
    "chars": 2753,
    "preview": "package cli\n\nimport (\n\t\"github.com/charmbracelet/bubbles/viewport\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.co"
  },
  {
    "path": "internal/cli/query.go",
    "chars": 2291,
    "preview": "package cli\n\nimport \"fmt\"\n\ntype QueryCmd struct {\n\tVars              variables         `flag:\"\" name:\"var\" help:\"Variabl"
  },
  {
    "path": "internal/cli/read_write_flag.go",
    "chars": 1560,
    "preview": "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/parsin"
  },
  {
    "path": "internal/cli/run.go",
    "chars": 2595,
    "preview": "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/v"
  },
  {
    "path": "internal/cli/variable.go",
    "chars": 2087,
    "preview": "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/das"
  },
  {
    "path": "internal/cli/version.go",
    "chars": 212,
    "preview": "package cli\n\nimport \"github.com/tomwright/dasel/v3/internal\"\n\ntype VersionCmd struct {\n}\n\nfunc (c *VersionCmd) Run(ctx *"
  },
  {
    "path": "internal/ptr/to.go",
    "chars": 99,
    "preview": "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",
    "chars": 216,
    "preview": "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 :="
  },
  {
    "path": "internal/version.go",
    "chars": 513,
    "preview": "package internal\n\nimport (\n\t\"runtime/debug\"\n)\n\n// Version represents the current version of dasel.\n// The real version n"
  },
  {
    "path": "model/README.md",
    "chars": 199,
    "preview": "# Model\n\nThe model package contains the Value struct and functionality for the application.\n\n`model.Value` is just a wra"
  },
  {
    "path": "model/error.go",
    "chars": 1209,
    "preview": "package model\n\nimport \"fmt\"\n\n// MapKeyNotFound is returned when a key is not found in a map.\ntype MapKeyNotFound struct "
  },
  {
    "path": "model/go_value.go",
    "chars": 960,
    "preview": "package model\n\nimport \"fmt\"\n\n// GoValue returns the value as a native Go value.\nfunc (v *Value) GoValue() (any, error) {"
  },
  {
    "path": "model/go_value_test.go",
    "chars": 1445,
    "preview": "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 g"
  },
  {
    "path": "model/orderedmap/map.go",
    "chars": 2376,
    "preview": "package orderedmap\n\nimport (\n\t\"reflect\"\n)\n\n// KeyValue is a single key value pair from a *Map.\ntype KeyValue struct {\n\tK"
  },
  {
    "path": "model/value.go",
    "chars": 7631,
    "preview": "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\tret"
  },
  {
    "path": "model/value_comparison.go",
    "chars": 5780,
    "preview": "package model\n\n// Compare compares two values.\nfunc (v *Value) Compare(other *Value) (int, error) {\n\teq, err := v.Equal("
  },
  {
    "path": "model/value_comparison_test.go",
    "chars": 18426,
    "preview": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\ntype compareTestCase struct {\n\ta   *m"
  },
  {
    "path": "model/value_literal.go",
    "chars": 4503,
    "preview": "package model\n\nimport (\n\t\"reflect\"\n\t\"slices\"\n)\n\nfunc newPtr() reflect.Value {\n\treturn reflect.New(reflect.TypeFor[any]()"
  },
  {
    "path": "model/value_literal_test.go",
    "chars": 211,
    "preview": "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) {"
  },
  {
    "path": "model/value_map.go",
    "chars": 5783,
    "preview": "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"
  },
  {
    "path": "model/value_map_test.go",
    "chars": 3533,
    "preview": "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/v"
  },
  {
    "path": "model/value_math.go",
    "chars": 5489,
    "preview": "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"
  },
  {
    "path": "model/value_math_test.go",
    "chars": 4608,
    "preview": "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\tr"
  },
  {
    "path": "model/value_metadata.go",
    "chars": 1594,
    "preview": "package model\n\n// MetadataValue returns a metadata value.\nfunc (v *Value) MetadataValue(key string) (any, bool) {\n\tif v."
  },
  {
    "path": "model/value_metadata_test.go",
    "chars": 660,
    "preview": "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)"
  },
  {
    "path": "model/value_set.go",
    "chars": 1376,
    "preview": "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) er"
  },
  {
    "path": "model/value_set_test.go",
    "chars": 5586,
    "preview": "package model_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/model\"\n)\n\ntype setTestCase struct {\n\tvalueFn   "
  },
  {
    "path": "model/value_slice.go",
    "chars": 4238,
    "preview": "package model\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n)\n\n// NewSliceValue returns a new slice value.\nfunc NewSliceValue() *Value {\n\t"
  },
  {
    "path": "model/value_slice_test.go",
    "chars": 4719,
    "preview": "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\tstand"
  },
  {
    "path": "model/value_test.go",
    "chars": 2771,
    "preview": "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"
  },
  {
    "path": "parsing/csv/csv.go",
    "chars": 1389,
    "preview": "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 r"
  },
  {
    "path": "parsing/csv/csv_test.go",
    "chars": 1510,
    "preview": "package csv\n\nimport (\n\t\"github.com/tomwright/dasel/v3/model\"\n\t\"testing\"\n)\n\nfunc TestValueToString(t *testing.T) {\n\ttests"
  },
  {
    "path": "parsing/csv/reader.go",
    "chars": 1496,
    "preview": "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/tomw"
  },
  {
    "path": "parsing/csv/reader_test.go",
    "chars": 1960,
    "preview": "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/"
  },
  {
    "path": "parsing/csv/writer.go",
    "chars": 1386,
    "preview": "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"
  },
  {
    "path": "parsing/csv/writer_test.go",
    "chars": 1807,
    "preview": "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/"
  },
  {
    "path": "parsing/d/reader.go",
    "chars": 831,
    "preview": "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/mod"
  },
  {
    "path": "parsing/format.go",
    "chars": 1176,
    "preview": "package parsing\n\nimport (\n\t\"fmt\"\n)\n\n// Format represents a file format.\ntype Format string\n\n// NewReader creates a new r"
  },
  {
    "path": "parsing/hcl/hcl.go",
    "chars": 331,
    "preview": "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 "
  },
  {
    "path": "parsing/hcl/reader.go",
    "chars": 5544,
    "preview": "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"
  },
  {
    "path": "parsing/hcl/reader_test.go",
    "chars": 1566,
    "preview": "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/p"
  },
  {
    "path": "parsing/hcl/writer.go",
    "chars": 4274,
    "preview": "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\"g"
  },
  {
    "path": "parsing/hcl/writer_test.go",
    "chars": 1274,
    "preview": "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\"githu"
  },
  {
    "path": "parsing/ini/ini.go",
    "chars": 247,
    "preview": "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 p"
  },
  {
    "path": "parsing/ini/ini_reader.go",
    "chars": 1528,
    "preview": "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/"
  },
  {
    "path": "parsing/ini/ini_test.go",
    "chars": 869,
    "preview": "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\"gi"
  },
  {
    "path": "parsing/ini/ini_writer.go",
    "chars": 2870,
    "preview": "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\""
  },
  {
    "path": "parsing/json/json.go",
    "chars": 430,
    "preview": "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 rep"
  },
  {
    "path": "parsing/json/json_reader.go",
    "chars": 3807,
    "preview": "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."
  },
  {
    "path": "parsing/json/json_test.go",
    "chars": 900,
    "preview": "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\"githu"
  },
  {
    "path": "parsing/json/json_writer.go",
    "chars": 4144,
    "preview": "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."
  },
  {
    "path": "parsing/reader.go",
    "chars": 722,
    "preview": "package parsing\n\nimport \"github.com/tomwright/dasel/v3/model\"\n\nvar readers = map[Format]NewReaderFn{}\n\ntype ReaderOption"
  },
  {
    "path": "parsing/toml/testdata/complex_example.toml",
    "chars": 1297,
    "preview": "# Comprehensive TOML example exercising many features\n\n\"quoted key\" = \"quoted value\"\n\n\"a.b\" = 42\n\ntitle = \"TOML Example\""
  },
  {
    "path": "parsing/toml/toml.go",
    "chars": 338,
    "preview": "package toml\n\nimport (\n\t\"github.com/tomwright/dasel/v3/parsing\"\n)\n\n// TODO : Implement using https://github.com/pelletie"
  },
  {
    "path": "parsing/toml/toml_reader.go",
    "chars": 10209,
    "preview": "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/da"
  },
  {
    "path": "parsing/toml/toml_reader_test.go",
    "chars": 17457,
    "preview": "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/tomwr"
  },
  {
    "path": "parsing/toml/toml_writer.go",
    "chars": 8252,
    "preview": "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"
  },
  {
    "path": "parsing/toml/toml_writer_test.go",
    "chars": 7918,
    "preview": "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"
  },
  {
    "path": "parsing/writer.go",
    "chars": 2069,
    "preview": "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]NewWriterF"
  },
  {
    "path": "parsing/xml/reader.go",
    "chars": 6713,
    "preview": "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\""
  },
  {
    "path": "parsing/xml/reader_test.go",
    "chars": 4151,
    "preview": "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/"
  },
  {
    "path": "parsing/xml/structured_comment_test.go",
    "chars": 12213,
    "preview": "package xml_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\tdaselxml \"github.com/tomwrig"
  },
  {
    "path": "parsing/xml/writer.go",
    "chars": 7058,
    "preview": "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/to"
  },
  {
    "path": "parsing/xml/writer_internal_test.go",
    "chars": 6302,
    "preview": "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/pars"
  },
  {
    "path": "parsing/xml/writer_test.go",
    "chars": 4540,
    "preview": "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"
  },
  {
    "path": "parsing/xml/xml.go",
    "chars": 852,
    "preview": "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 p"
  },
  {
    "path": "parsing/yaml/yaml.go",
    "chars": 470,
    "preview": "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"
  },
  {
    "path": "parsing/yaml/yaml_reader.go",
    "chars": 4667,
    "preview": "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/t"
  },
  {
    "path": "parsing/yaml/yaml_test.go",
    "chars": 9828,
    "preview": "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/t"
  },
  {
    "path": "parsing/yaml/yaml_writer.go",
    "chars": 2832,
    "preview": "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"
  },
  {
    "path": "selector/README.md",
    "chars": 127,
    "preview": "# Selector\n\nThe selector package contains everything needed to parse a selector string into an AST, which we can then ex"
  },
  {
    "path": "selector/ast/ast.go",
    "chars": 648,
    "preview": "package ast\n\ntype Program struct {\n\tStatements []Statement\n}\n\ntype Statement struct {\n\tExpressions Expr\n}\n\ntype Expressi"
  },
  {
    "path": "selector/ast/ast_test.go",
    "chars": 720,
    "preview": "package ast\n\nimport \"testing\"\n\n// TestExpr_expr tests the expr method of all the types in the ast package.\n// Note that "
  },
  {
    "path": "selector/ast/expression_complex.go",
    "chars": 2214,
    "preview": "package ast\n\nimport \"github.com/tomwright/dasel/v3/selector/lexer\"\n\ntype BinaryExpr struct {\n\tLeft     Expr\n\tOperator le"
  },
  {
    "path": "selector/ast/expression_literal.go",
    "chars": 451,
    "preview": "package ast\n\nimport \"regexp\"\n\ntype NumberFloatExpr struct {\n\tValue float64\n}\n\nfunc (NumberFloatExpr) expr() {}\n\ntype Num"
  },
  {
    "path": "selector/lexer/token.go",
    "chars": 1762,
    "preview": "package lexer\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n)\n\ntype TokenKind int\n\nfunc TokenKinds(tk ...TokenKind) []TokenKind {\n\treturn t"
  },
  {
    "path": "selector/lexer/tokenize.go",
    "chars": 8164,
    "preview": "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"
  },
  {
    "path": "selector/lexer/tokenize_test.go",
    "chars": 3588,
    "preview": "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 stru"
  },
  {
    "path": "selector/parser/denotations.go",
    "chars": 1877,
    "preview": "package parser\n\nimport \"github.com/tomwright/dasel/v3/selector/lexer\"\n\n// left denotation tokens are tokens that expect "
  },
  {
    "path": "selector/parser/error.go",
    "chars": 480,
    "preview": "package parser\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)\n\ntype PositionalError struct {\n\tPosit"
  },
  {
    "path": "selector/parser/parse_array.go",
    "chars": 2061,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_branch.go",
    "chars": 587,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_each.go",
    "chars": 460,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_filter.go",
    "chars": 574,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_func.go",
    "chars": 686,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_group.go",
    "chars": 402,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_if.go",
    "chars": 1816,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_literal.go",
    "chars": 1834,
    "preview": "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."
  },
  {
    "path": "selector/parser/parse_map.go",
    "chars": 559,
    "preview": "package parser\n\nimport (\n\t\"github.com/tomwright/dasel/v3/selector/ast\"\n\t\"github.com/tomwright/dasel/v3/selector/lexer\"\n)"
  },
  {
    "path": "selector/parser/parse_object.go",
    "chars": 2088,
    "preview": "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/"
  }
]

// ... and 9 more files (download for full content)

About this extraction

This page contains the full source code of the TomWright/dasel GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 209 files (499.4 KB), approximately 160.6k tokens, and a symbol index with 723 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!