Full Code of wagoodman/dive for AI

main d6c691947f8f cached
170 files
591.2 KB
206.5k tokens
716 symbols
1 requests
Download .txt
Showing preview only (638K chars total). Download the full file or copy to clipboard to get everything.
Repository: wagoodman/dive
Branch: main
Commit: d6c691947f8f
Files: 170
Total size: 591.2 KB

Directory structure:
gitextract_iuihkmt4/

├── .binny.yaml
├── .bouncer.yaml
├── .data/
│   ├── .dive-ci
│   ├── Dockerfile.example
│   ├── Dockerfile.minimal
│   └── Dockerfile.test-image
├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── actions/
│   │   └── bootstrap/
│   │       └── action.yaml
│   ├── dependabot.yaml
│   ├── scripts/
│   │   ├── coverage.py
│   │   └── trigger-release.sh
│   └── workflows/
│       ├── release.yaml
│       └── validations.yaml
├── .gitignore
├── .golangci.yaml
├── .goreleaser.yaml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── RELEASE.md
├── Taskfile.yaml
├── cmd/
│   └── dive/
│       ├── cli/
│       │   ├── cli.go
│       │   ├── cli_build_test.go
│       │   ├── cli_ci_test.go
│       │   ├── cli_config_test.go
│       │   ├── cli_json_test.go
│       │   ├── cli_load_test.go
│       │   ├── cli_test.go
│       │   ├── internal/
│       │   │   ├── command/
│       │   │   │   ├── adapter/
│       │   │   │   │   ├── analyzer.go
│       │   │   │   │   ├── evaluator.go
│       │   │   │   │   ├── exporter.go
│       │   │   │   │   └── resolver.go
│       │   │   │   ├── build.go
│       │   │   │   ├── ci/
│       │   │   │   │   ├── evaluator.go
│       │   │   │   │   ├── evaluator_test.go
│       │   │   │   │   ├── rule.go
│       │   │   │   │   └── rules.go
│       │   │   │   ├── export/
│       │   │   │   │   ├── export.go
│       │   │   │   │   ├── export_test.go
│       │   │   │   │   ├── main_test.go
│       │   │   │   │   └── testdata/
│       │   │   │   │       └── snapshots/
│       │   │   │   │           └── export_test.snap
│       │   │   │   └── root.go
│       │   │   ├── options/
│       │   │   │   ├── analysis.go
│       │   │   │   ├── application.go
│       │   │   │   ├── ci.go
│       │   │   │   ├── ci_rules.go
│       │   │   │   ├── export.go
│       │   │   │   ├── ui.go
│       │   │   │   ├── ui_diff.go
│       │   │   │   ├── ui_filetree.go
│       │   │   │   ├── ui_keybindings.go
│       │   │   │   └── ui_layers.go
│       │   │   └── ui/
│       │   │       ├── no_ui.go
│       │   │       ├── v1/
│       │   │       │   ├── app/
│       │   │       │   │   ├── app.go
│       │   │       │   │   ├── controller.go
│       │   │       │   │   ├── job_control_other.go
│       │   │       │   │   └── job_control_unix.go
│       │   │       │   ├── config.go
│       │   │       │   ├── format/
│       │   │       │   │   └── format.go
│       │   │       │   ├── key/
│       │   │       │   │   ├── binding.go
│       │   │       │   │   └── config.go
│       │   │       │   ├── layout/
│       │   │       │   │   ├── area.go
│       │   │       │   │   ├── compound/
│       │   │       │   │   │   └── layer_details_column.go
│       │   │       │   │   ├── layout.go
│       │   │       │   │   ├── location.go
│       │   │       │   │   ├── manager.go
│       │   │       │   │   └── manager_test.go
│       │   │       │   ├── view/
│       │   │       │   │   ├── cursor.go
│       │   │       │   │   ├── debug.go
│       │   │       │   │   ├── filetree.go
│       │   │       │   │   ├── filter.go
│       │   │       │   │   ├── image_details.go
│       │   │       │   │   ├── layer.go
│       │   │       │   │   ├── layer_change_listener.go
│       │   │       │   │   ├── layer_details.go
│       │   │       │   │   ├── renderer.go
│       │   │       │   │   ├── status.go
│       │   │       │   │   └── views.go
│       │   │       │   └── viewmodel/
│       │   │       │       ├── config.go
│       │   │       │       ├── filetree.go
│       │   │       │       ├── filetree_test.go
│       │   │       │       ├── layer_compare.go
│       │   │       │       ├── layer_selection.go
│       │   │       │       ├── layer_set_state.go
│       │   │       │       ├── layer_set_state_test.go
│       │   │       │       └── testdata/
│       │   │       │           ├── TestFileShowAggregateChanges.txt
│       │   │       │           ├── TestFileTreeDirCollapse.txt
│       │   │       │           ├── TestFileTreeDirCollapseAll.txt
│       │   │       │           ├── TestFileTreeDirCursorRight.txt
│       │   │       │           ├── TestFileTreeFilterTree.txt
│       │   │       │           ├── TestFileTreeGoCase.txt
│       │   │       │           ├── TestFileTreeHideAddedRemovedModified.txt
│       │   │       │           ├── TestFileTreeHideTypeWithFilter.txt
│       │   │       │           ├── TestFileTreeHideUnmodified.txt
│       │   │       │           ├── TestFileTreeNoAttributes.txt
│       │   │       │           ├── TestFileTreePageDown.txt
│       │   │       │           ├── TestFileTreePageUp.txt
│       │   │       │           ├── TestFileTreeRestrictedHeight.txt
│       │   │       │           └── TestFileTreeSelectLayer.txt
│       │   │       └── v1.go
│       │   └── testdata/
│       │       ├── config/
│       │       │   └── dive-ci-legacy.yaml
│       │       ├── default-ci-config/
│       │       │   └── .dive-ci
│       │       ├── dive-enable-ci.yaml
│       │       ├── image-multi-layer-containerfile/
│       │       │   ├── Containerfile
│       │       │   ├── dive-pass.yaml
│       │       │   ├── example.md
│       │       │   └── overwrite.md
│       │       ├── image-multi-layer-dockerfile/
│       │       │   ├── Dockerfile
│       │       │   ├── dive-fail.yaml
│       │       │   ├── dive-pass.yaml
│       │       │   ├── example.md
│       │       │   └── overwrite.md
│       │       ├── invalid/
│       │       │   └── Dockerfile
│       │       └── snapshots/
│       │           ├── cli_build_test.snap
│       │           ├── cli_ci_test.snap
│       │           ├── cli_config_test.snap
│       │           ├── cli_json_test.snap
│       │           └── cli_load_test.snap
│       └── main.go
├── dive/
│   ├── filetree/
│   │   ├── comparer.go
│   │   ├── diff.go
│   │   ├── efficiency.go
│   │   ├── efficiency_test.go
│   │   ├── file_info.go
│   │   ├── file_node.go
│   │   ├── file_node_test.go
│   │   ├── file_tree.go
│   │   ├── file_tree_test.go
│   │   ├── node_data.go
│   │   ├── node_data_test.go
│   │   ├── order_strategy.go
│   │   ├── path_error.go
│   │   └── view_info.go
│   ├── get_image_resolver.go
│   └── image/
│       ├── analysis.go
│       ├── docker/
│       │   ├── archive_resolver.go
│       │   ├── build.go
│       │   ├── build_test.go
│       │   ├── cli.go
│       │   ├── config.go
│       │   ├── docker_host_unix.go
│       │   ├── docker_host_windows.go
│       │   ├── engine_resolver.go
│       │   ├── image_archive.go
│       │   ├── image_archive_analysis_test.go
│       │   ├── layer.go
│       │   ├── manifest.go
│       │   └── testing.go
│       ├── image.go
│       ├── layer.go
│       ├── podman/
│       │   ├── build.go
│       │   ├── cli.go
│       │   ├── resolver.go
│       │   └── resolver_unsupported.go
│       └── resolver.go
├── go.mod
├── go.sum
└── internal/
    ├── bus/
    │   ├── bus.go
    │   ├── event/
    │   │   ├── event.go
    │   │   ├── parser/
    │   │   │   └── parsers.go
    │   │   └── payload/
    │   │       ├── explore.go
    │   │       └── generic.go
    │   └── helpers.go
    ├── log/
    │   └── log.go
    └── utils/
        ├── format.go
        └── view.go

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

================================================
FILE: .binny.yaml
================================================
tools:
  # we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
  - name: binny
    version:
      want: v0.9.0
    method: github-release
    with:
      repo: anchore/binny

  # used for linting
  - name: golangci-lint
    version:
      want: v1.64.8
    method: github-release
    with:
      repo: golangci/golangci-lint

  # used for showing the changelog at release
  - name: glow
    version:
      want: v2.1.0
    method: github-release
    with:
      repo: charmbracelet/glow

  # used to release all artifacts
  - name: goreleaser
    version:
      want: v2.8.1
    method: github-release
    with:
      repo: goreleaser/goreleaser

  # used at release to generate the changelog
  - name: chronicle
    version:
      want: v0.8.0
    method: github-release
    with:
      repo: anchore/chronicle

  # used during static analysis for license compliance
  - name: bouncer
    version:
      want: v0.4.0
    method: github-release
    with:
      repo: wagoodman/go-bouncer

  # used for running all local and CI tasks
  - name: task
    version:
      want: v3.42.1
    method: github-release
    with:
      repo: go-task/task

  # used for triggering a release
  - name: gh
    version:
      want: v2.69.0
    method: github-release
    with:
      repo: cli/cli


================================================
FILE: .bouncer.yaml
================================================
permit:
  - BSD.*
  - MIT.*
  - Apache.*
  - MPL.*
  - ISC
  - WTFPL
  - Unlicense

ignore-packages:
  # crypto/internal/boring is released under the openSSL license as a part of the Golang Standard Library
  - crypto/internal/boring



================================================
FILE: .data/.dive-ci
================================================
---
plugins:
  - plugin1

rules:
  # If the efficiency is measured below X%, mark as failed. (expressed as a percentage between 0-1)
  lowestEfficiency: 0.95

  # If the amount of wasted space is at least X or larger than X, mark as failed. (expressed in B, KB, MB, and GB)
  highestWastedBytes: 20Mb

  # If the amount of wasted space makes up for X% of the image, mark as failed. (fail if the threshold is met or crossed; expressed as a percentage between 0-1)
  highestUserWastedPercent: 0.5

  plugin1/rule1: error


================================================
FILE: .data/Dockerfile.example
================================================
FROM busybox:latest
ADD README.md /somefile.txt
RUN mkdir -p /root/example/really/nested
RUN cp /somefile.txt /root/example/somefile1.txt
RUN chmod 444 /root/example/somefile1.txt
RUN cp /somefile.txt /root/example/somefile2.txt
RUN cp /somefile.txt /root/example/somefile3.txt
RUN mv /root/example/somefile3.txt /root/saved.txt
RUN cp /root/saved.txt /root/.saved.txt
RUN rm -rf /root/example/
ADD .scripts/ /root/.data/
RUN cp /root/saved.txt /tmp/saved.again1.txt
RUN cp /root/saved.txt /root/.data/saved.again2.txt
RUN chmod +x /root/saved.txt
RUN chmod 421 /root


================================================
FILE: .data/Dockerfile.minimal
================================================
FROM scratch
COPY README.md /README.md


================================================
FILE: .data/Dockerfile.test-image
================================================
FROM busybox:latest
ADD README.md /somefile.txt
RUN mkdir -p /root/example/really/nested
RUN cp /somefile.txt /root/example/somefile1.txt
RUN chmod 444 /root/example/somefile1.txt
RUN cp /somefile.txt /root/example/somefile2.txt
RUN cp /somefile.txt /root/example/somefile3.txt
RUN mv /root/example/somefile3.txt /root/saved.txt
RUN cp /root/saved.txt /root/.saved.txt
RUN rm -rf /root/example/
ADD .scripts/ /root/.data/
RUN cp /root/saved.txt /tmp/saved.again1.txt
RUN cp /root/saved.txt /root/.data/saved.again2.txt
RUN chmod +x /root/saved.txt


================================================
FILE: .dockerignore
================================================
/.git
/.data
/.cover
/dist
!/dist/dive_linux_amd64
/ui
/internal/utils
/image
/cmd
/build
coverage.txt


================================================
FILE: .github/FUNDING.yml
================================================
github: ['wagoodman']


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Something isn't working as expected
title: ''
labels: bug
assignees: ''

---

**What happened**:

**What you expected to happen**:

**How to reproduce it (as minimally and precisely as possible)**:

**Anything else we need to know?**:

**Environment**:
- OS version
- Docker version (if applicable)


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Got an idea for a new feature? Let us know!
title: ''
labels: enhancement
assignees: ''

---

**What would you like to be added**:

**Why is this needed**:

**Additional context**:
<!-- Add any other context or screenshots about the feature request here. -->


================================================
FILE: .github/actions/bootstrap/action.yaml
================================================
name: "Bootstrap"
description: "Bootstrap all tools and dependencies"
inputs:
  go-version:
    description: "Go version to install"
    required: true
    default: "1.24.x"
  cache-key-prefix:
    description: "Prefix all cache keys with this value"
    required: true
    default: "efa04b89c1b1"
  bootstrap-apt-packages:
    description: "Space delimited list of tools to install via apt"
    default: ""

runs:
  using: "composite"
  steps:
    - uses: actions/setup-go@v5
      with:
        go-version: ${{ inputs.go-version }}

    - name: Restore tool cache
      id: tool-cache
      uses: actions/cache@v4
      with:
        path: ${{ github.workspace }}/.tmp
        key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }}

    - name: (cache-miss) Bootstrap project tools
      shell: bash
      if: steps.tool-cache.outputs.cache-hit != 'true'
      run: make tools

    - name: (cache-miss) Bootstrap go dependencies
      shell: bash
      if: steps.go-mod-cache.outputs.cache-hit != 'true'
      run: go mod download -x

    - name: Install apt packages
      if: inputs.bootstrap-apt-packages != ''
      shell: bash
      run: |
        DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }}


================================================
FILE: .github/dependabot.yaml
================================================
version: 2
updates:
  - package-ecosystem: "gomod"
    directory: "/"
    schedule:
      interval: "weekly"
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"


================================================
FILE: .github/scripts/coverage.py
================================================
#!/usr/bin/env python3
import subprocess
import sys
import shlex


class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'


if len(sys.argv) < 3:
    print("Usage: coverage.py [threshold] [go-coverage-report]")
    sys.exit(1)


threshold = float(sys.argv[1])
report = sys.argv[2]


args = shlex.split(f"go tool cover -func {report}")
p = subprocess.run(args, capture_output=True, text=True)

percent_coverage = float(p.stdout.splitlines()[-1].split()[-1].replace("%", ""))
print(f"{bcolors.BOLD}Coverage: {percent_coverage}%{bcolors.ENDC}")

if percent_coverage < threshold:
    print(f"{bcolors.BOLD}{bcolors.FAIL}Coverage below threshold of {threshold}%{bcolors.ENDC}")
    sys.exit(1)


================================================
FILE: .github/scripts/trigger-release.sh
================================================
#!/usr/bin/env bash
set -eu

bold=$(tput bold)
normal=$(tput sgr0)

if ! [ -x "$(command -v gh)" ]; then
    echo "The GitHub CLI could not be found. To continue follow the instructions at https://github.com/cli/cli#installation"
    exit 1
fi

gh auth status

# we need all of the git state to determine the next version. Since tagging is done by
# the release pipeline it is possible to not have all of the tags from previous releases.
git fetch --tags

# populates the CHANGELOG.md and VERSION files
echo "${bold}Generating changelog...${normal}"
make changelog 2> /dev/null

NEXT_VERSION=$(cat VERSION)

if [[ "$NEXT_VERSION" == "" ||  "${NEXT_VERSION}" == "(Unreleased)" ]]; then
    echo "Could not determine the next version to release. Exiting..."
    exit 1
fi

while true; do
    read -p "${bold}Do you want to trigger a release for version '${NEXT_VERSION}'?${normal} [y/n] " yn
    case $yn in
        [Yy]* ) echo; break;;
        [Nn]* ) echo; echo "Cancelling release..."; exit;;
        * ) echo "Please answer yes or no.";;
    esac
done

echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..."
echo
gh workflow run release.yaml -f version=${NEXT_VERSION}

echo
echo "${bold}Waiting for release to start...${normal}"
sleep 10

set +e

echo "${bold}Head to the release workflow to monitor the release:${normal} $(gh run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
id=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
gh run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" gh run view $id --log-failed)


================================================
FILE: .github/workflows/release.yaml
================================================
name: "Release"
on:
  workflow_dispatch:
    inputs:
      version:
        description: tag the latest commit on main with the given version (prefixed with v)
        required: true

jobs:
  quality-gate:
    environment: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check if tag already exists
        # note: this will fail if the tag already exists
        run: |
          [[ "${{ github.event.inputs.version }}" == v* ]] || (echo "version '${{ github.event.inputs.version }}' does not have a 'v' prefix" && exit 1)
          git tag ${{ github.event.inputs.version }}

      - name: Check static analysis results
        uses: fountainhead/action-wait-for-check@v1.2.0
        id: static-analysis
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          # This check name is defined as the github action job name (in .github/workflows/validations.yaml)
          checkName: "Static analysis"
          ref: ${{ github.event.pull_request.head.sha || github.sha }}

      - name: Check unit test results
        uses: fountainhead/action-wait-for-check@v1.2.0
        id: unit
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          # This check name is defined as the github action job name (in .github/workflows/validations.yaml)
          checkName: "Unit tests (ubuntu-latest)"
          ref: ${{ github.event.pull_request.head.sha || github.sha }}

      - name: Check acceptance test results (linux)
        uses: fountainhead/action-wait-for-check@v1.2.0
        id: acceptance-linux
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          # This check name is defined as the github action job name (in .github/workflows/validations.yaml)
          checkName: "Acceptance tests (Linux)"
          ref: ${{ github.event.pull_request.head.sha || github.sha }}

      - name: Check acceptance test results (mac)
        uses: fountainhead/action-wait-for-check@v1.2.0
        id: acceptance-mac
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          # This check name is defined as the github action job name (in .github/workflows/validations.yaml)
          checkName: "Acceptance tests (Mac)"
          ref: ${{ github.event.pull_request.head.sha || github.sha }}

      - name: Check acceptance test results (windows)
        uses: fountainhead/action-wait-for-check@v1.2.0
        id: acceptance-windows
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          # This check name is defined as the github action job name (in .github/workflows/validations.yaml)
          checkName: "Acceptance tests (Windows)"
          ref: ${{ github.event.pull_request.head.sha || github.sha }}

      - name: Quality gate
        if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success' || steps.acceptance-windows.outputs.conclusion != 'success'
        run: |
          echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}"
          echo "Unit Test Status: ${{ steps.unit.outputs.conclusion }}"
          echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}"
          echo "Acceptance Test (Mac) Status: ${{ steps.acceptance-mac.outputs.conclusion }}"
          echo "Acceptance Test (Windows) Status: ${{ steps.acceptance-windows.outputs.conclusion }}"

          false

  release:
    needs: [quality-gate]
    runs-on: ubuntu-latest
    permissions:
      # for tagging
      contents: write
      # for pushing container images
      packages: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Bootstrap environment
        uses: ./.github/actions/bootstrap

      - name: Login to Docker Hub
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772  #v3.4.0
        with:
          registry: docker.io
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772  #v3.4.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Tag release
        run: |
          git tag ${{ github.event.inputs.version }}
          git push origin --tags
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Build & publish release artifacts
        run: make ci-release
        env:
          # for creating the release (requires write access to content)
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          # for updating brew formula in wagoodman/homebrew-dive
          TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}

      - name: Smoke test published image
        run: make ci-test-docker-image


================================================
FILE: .github/workflows/validations.yaml
================================================
name: "Validations"
on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:

jobs:
  Static-Analysis:
    # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
    name: "Static analysis"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Bootstrap environment
        uses: ./.github/actions/bootstrap

      - name: Run static analysis
        run: make static-analysis

  Unit-Test:
    # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
    name: "Unit tests"
    strategy:
      matrix:
        platform:
          - ubuntu-latest
    #         - macos-latest # todo: mac runners are expensive minute-wise
    #         - windows-latest # todo: support windows

    runs-on: ${{ matrix.platform }}
    steps:
      - uses: actions/checkout@v4

      - name: Bootstrap environment
        uses: ./.github/actions/bootstrap

      - name: Run unit tests
        run: make unit

  Build-Snapshot-Artifacts:
    name: "Build snapshot artifacts"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Bootstrap environment
        uses: ./.github/actions/bootstrap

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build snapshot artifacts
        run: make snapshot

      - run: docker images wagoodman/dive

      # todo: compare against known json output in shared volume
      - name: Test production image
        run: make ci-test-docker-image

      # why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
      # see https://github.com/actions/upload-artifact/issues/199 for more info
      - name: Upload snapshot artifacts
        uses: actions/cache/save@v4
        with:
          path: snapshot
          key: snapshot-build-${{ github.run_id }}

      # ... however the cache trick doesn't work on windows :(
      - uses: actions/upload-artifact@v4
        with:
          name: windows-artifacts
          path: snapshot/dive_windows_amd64_v1/dive.exe

  Acceptance-Linux:
    name: "Acceptance tests (Linux)"
    needs: [Build-Snapshot-Artifacts]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master

      - name: Download snapshot build
        uses: actions/cache/restore@v4
        with:
          path: snapshot
          key: snapshot-build-${{ github.run_id }}

      - name: Test linux run
        run: make ci-test-linux-run

      - name: Test DEB package installation
        run: make ci-test-deb-package-install

      - name: Test RPM package installation
        run: make ci-test-rpm-package-install

  Acceptance-Mac:
    name: "Acceptance tests (Mac)"
    needs: [Build-Snapshot-Artifacts]
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@master

      - name: Download snapshot build
        uses: actions/cache/restore@v4
        with:
          path: snapshot
          key: snapshot-build-${{ github.run_id }}

      - name: Test darwin run
        run: make ci-test-mac-run

  Acceptance-Windows:
    name: "Acceptance tests (Windows)"
    needs: [Build-Snapshot-Artifacts]
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@master

      - uses: actions/download-artifact@v4
        with:
          name: windows-artifacts

      - name: Test windows run
        run: make ci-test-windows-run


================================================
FILE: .gitignore
================================================
# app configs
.dive.yaml

# misc
/.image
*.log
CHANGELOG.md
VERSION

# IDEs
/.idea
/.vscode

# tooling
/bin
/.tool-versions
/.tmp
/.tool
/.mise.toml
/.task
/go.work
/go.work.sum

# builds
/dist
/snapshot

# testing
.cover
coverage.txt

# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
/tmp
/build
/_vendor*
/vendor


================================================
FILE: .golangci.yaml
================================================
# TODO: enable this when we have coverage on docstring comments
#issues:
#  # The list of ids of default excludes to include or disable.
#  include:
#    - EXC0002 # disable excluding of issues about comments from golint

linters-settings:
  funlen:
    # Checks the number of lines in a function.
    # If lower than 0, disable the check.
    # Default: 60
    # TODO: drop this down over time...
    lines: 110
    # Checks the number of statements in a function.
    # If lower than 0, disable the check.
    # Default: 40
    statements: 60

# TODO: use the default linters for now, but include these over time
#linters:
#  # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
#  disable-all: true
#  enable:
#    - asciicheck
#    - bodyclose
#    - depguard
#    - dogsled
#    - dupl
#    - errcheck
#    - exportloopref
#    - funlen
#    - gocognit
#    - goconst
#    - gocritic
#    - gocyclo
#    - gofmt
#    - goimports
#    - goprintffuncname
#    - gosec
#    - gosimple
#    - govet
#    - ineffassign
#    - misspell
#    - nakedret
#    - nolintlint
#    - revive
#    - staticcheck
#    - stylecheck
#    - typecheck
#    - unconvert
#    - unparam
#    - unused
#    - whitespace

# do not enable...
#    - gochecknoglobals
#    - gochecknoinits    # this is too aggressive
#    - godot
#    - godox
#    - goerr113
#    - golint       # deprecated
#    - gomnd        # this is too aggressive
#    - interfacer   # this is a good idea, but is no longer supported and is prone to false positives
#    - lll          # without a way to specify per-line exception cases, this is not usable
#    - maligned     # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
#    - nestif
#    - prealloc     # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code
#    - scopelint    # deprecated
#    - testpackage
#    - wsl          # this doesn't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
#    - varcheck     # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter.  Replaced by unused.
#    - deadcode     # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter.  Replaced by unused.
#    - structcheck  # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter.  Replaced by unused.
#    - rowserrcheck # we're not using sql.Rows at all in the codebase


================================================
FILE: .goreleaser.yaml
================================================
version: 2

release:
  # If set to auto, will mark the release as not ready for production in case there is an indicator for this in the
  # tag e.g. v1.0.0-rc1 .If set to true, will mark the release as not ready for production.
  prerelease: auto

  # If set to true, will not auto-publish the release. This is done to allow us to review the changelog before publishing.
  draft: false

env:
  # required to support multi architecture docker builds
  - DOCKER_CLI_EXPERIMENTAL=enabled
  - CGO_ENABLED=0

builds:
  - binary: dive
    dir: ./cmd/dive
    env:
      - CGO_ENABLED=0
    goos:
      - windows
      - darwin
      - linux
    goarch:
      - amd64
      - arm64
      - ppc64le
    ldflags:
      -w
      -s
      -extldflags '-static'
      -X main.version={{.Version}}
      -X main.gitCommit={{.Commit}}
      -X main.buildDate={{.Date}}
      -X main.gitDescription={{.Summary}}

brews:
  - repository:
      owner: wagoodman
      name: homebrew-dive
      token: "{{.Env.TAP_GITHUB_TOKEN}}"
    homepage: &project_url "https://github.com/wagoodman/dive/"
    description: &description "A tool for exploring layers in a docker image"

archives:
  - format: tar.gz
    format_overrides:
      - goos: windows
        format: zip

nfpms:
  - license: MIT
    maintainer: Alex Goodman
    homepage: *project_url
    description: *description
    formats:
      - rpm
      - deb

dockers:
  # docker.io amd64
  - &dockerhub_amd64
    id: docker-amd64
    ids:
      - dive
    use: buildx
    goarch: amd64
    image_templates:
      - docker.io/wagoodman/dive:latest
      - docker.io/wagoodman/dive:v{{.Version}}-amd64
    build_flag_templates:
      - "--build-arg=DOCKER_CLI_VERSION={{.Env.DOCKER_CLI_VERSION}}"
      - "--platform=linux/amd64"
      - "--label=org.opencontainers.image.created={{.Date}}"
      - "--label=org.opencontainers.image.title={{.ProjectName}}"
      - "--label=org.opencontainers.image.description=A tool for exploring layers in a docker image"
      - "--label=org.opencontainers.image.url={{.GitURL}}"
      - "--label=org.opencontainers.image.source={{.GitURL}}"
      - "--label=org.opencontainers.image.version={{.Version}}"
      - "--label=org.opencontainers.image.revision={{.FullCommit}}"
      - "--label=org.opencontainers.image.licenses=MIT"
      - "--label=org.opencontainers.image.authors=Alex Goodman <@wagoodman>"

  # docker.io arm64
  - &dockerhub_arm64
    id: docker-arm64
    ids:
      - dive
    use: buildx
    goarch: arm64
    image_templates:
      - docker.io/wagoodman/dive:v{{.Version}}-arm64
    build_flag_templates:
      - "--build-arg=DOCKER_CLI_VERSION={{.Env.DOCKER_CLI_VERSION}}"
      - "--platform=linux/arm64/v8"
      - "--label=org.opencontainers.image.created={{.Date}}"
      - "--label=org.opencontainers.image.title={{.ProjectName}}"
      - "--label=org.opencontainers.image.description=A tool for exploring layers in a docker image"
      - "--label=org.opencontainers.image.url={{.GitURL}}"
      - "--label=org.opencontainers.image.source={{.GitURL}}"
      - "--label=org.opencontainers.image.version={{.Version}}"
      - "--label=org.opencontainers.image.revision={{.FullCommit}}"
      - "--label=org.opencontainers.image.licenses=MIT"
      - "--label=org.opencontainers.image.authors=Alex Goodman <@wagoodman>"

  # ghcr.io amd64
  - id: ghcr-amd64
    <<: *dockerhub_amd64
    image_templates:
      - ghcr.io/wagoodman/dive:v{{.Version}}-amd64

  # ghcr.io arm64
  - id: ghcr-arm64
    <<: *dockerhub_arm64
    image_templates:
      - ghcr.io/wagoodman/dive:v{{.Version}}-arm64

docker_manifests:
  # docker.io manifests
  - name_template: docker.io/wagoodman/dive:latest
    image_templates: &dockerhub_images
      - docker.io/wagoodman/dive:v{{.Version}}-amd64
      - docker.io/wagoodman/dive:v{{.Version}}-arm64

  - name_template: docker.io/wagoodman/dive:v{{.Major}}
    image_templates: *dockerhub_images

  - name_template: docker.io/wagoodman/dive:v{{.Major}}.{{.Minor}}
    image_templates: *dockerhub_images

  - name_template: docker.io/wagoodman/dive:v{{.Version}}
    image_templates: *dockerhub_images

  # ghcr.io manifests
  - name_template: ghcr.io/wagoodman/dive:latest
    image_templates: &ghcr_images
      - ghcr.io/wagoodman/dive:v{{.Version}}-amd64
      - ghcr.io/wagoodman/dive:v{{.Version}}-arm64

  - name_template: ghcr.io/wagoodman/dive:v{{.Major}}
    image_templates: *ghcr_images

  - name_template: ghcr.io/wagoodman/dive:v{{.Major}}.{{.Minor}}
    image_templates: *ghcr_images

  - name_template: ghcr.io/wagoodman/dive:v{{.Version}}
    image_templates: *ghcr_images


================================================
FILE: Dockerfile
================================================
FROM alpine:3.21 AS base

ARG DOCKER_CLI_VERSION=${DOCKER_CLI_VERSION}
RUN wget -O- https://download.docker.com/linux/static/stable/$(uname -m)/docker-${DOCKER_CLI_VERSION}.tgz | \
    tar -xzf - docker/docker --strip-component=1 -C /usr/local/bin

COPY dive /usr/local/bin/

# though we could make this a multi-stage image and copy the binary to scratch, this image is small enough
# and users are expecting to be able to exec into it
ENTRYPOINT ["/usr/local/bin/dive"]


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

Copyright (c) 2018 Alex Goodman

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: Makefile
================================================
OWNER = wagoodman
PROJECT = dive

TOOL_DIR = .tool
BINNY = $(TOOL_DIR)/binny
TASK = $(TOOL_DIR)/task

.DEFAULT_GOAL := make-default

## Bootstrapping targets #################################

# note: we need to assume that binny and task have not already been installed
$(BINNY):
	@mkdir -p $(TOOL_DIR)
	@curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh -s -- -b $(TOOL_DIR)

# note: we need to assume that binny and task have not already been installed
.PHONY: task
$(TASK) task: $(BINNY)
	@$(BINNY) install task -q

# this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again
%:
	@make --silent $(TASK)
	@$(TASK) $@

## Shim targets #################################

.PHONY: make-default
make-default: $(TASK)
	@# run the default task in the taskfile
	@$(TASK)

# for those of us that can't seem to kick the habit of typing `make ...` lets wrap the superior `task` tool
TASKS := $(shell bash -c "test -f $(TASK) && $(TASK) -l | grep '^\* ' | cut -d' ' -f2 | tr -d ':' | tr '\n' ' '" ) $(shell bash -c "test -f $(TASK) && $(TASK) -l | grep 'aliases:' | cut -d ':' -f 3 | tr '\n' ' ' | tr -d ','")

.PHONY: $(TASKS)
$(TASKS): $(TASK)
	@$(TASK) $@

## actual targets

ci-test-windows-run:
	dive.exe --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci

help: $(TASK)
	@$(TASK) -l


================================================
FILE: README.md
================================================
# dive
[![GitHub release](https://img.shields.io/github/release/wagoodman/dive.svg)](https://github.com/wagoodman/dive/releases/latest)
[![Validations](https://github.com/wagoodman/dive/actions/workflows/validations.yaml/badge.svg)](https://github.com/wagoodman/dive/actions/workflows/validations.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/wagoodman/dive)](https://goreportcard.com/report/github.com/wagoodman/dive)
[![License: MIT](https://img.shields.io/badge/License-MIT%202.0-blue.svg)](https://github.com/wagoodman/dive/blob/main/LICENSE)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?style=flat)](https://www.paypal.me/wagoodman)

**A tool for exploring a Docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.**


![Image](.data/demo.gif)

To analyze a Docker image simply run dive with an image tag/id/digest:
```bash
dive <your-image-tag>
```

or you can dive with Docker directly:
```
alias dive="docker run -ti --rm  -v /var/run/docker.sock:/var/run/docker.sock docker.io/wagoodman/dive"
dive <your-image-tag>

# for example
dive nginx:latest
```

or if you want to build your image then jump straight into analyzing it:
```bash
dive build -t <some-tag> .
```

Building on macOS (supporting only the Docker container engine):

```bash
docker run --rm -it \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v  "$(pwd)":"$(pwd)" \
      -w "$(pwd)" \
      -v "$HOME/.dive.yaml":"$HOME/.dive.yaml" \
      docker.io/wagoodman/dive:latest build -t <some-tag> .
```

Additionally you can run this in your CI pipeline to ensure you're keeping wasted space to a minimum (this skips the UI):
```
CI=true dive <your-image>
```

![Image](.data/demo-ci.png)

**This is beta quality!** *Feel free to submit an issue if you want a new feature or find a bug :)*

## Basic Features

**Show Docker image contents broken down by layer**

As you select a layer on the left, you are shown the contents of that layer combined with all previous layers on the right. Also, you can fully explore the file tree with the arrow keys.

**Indicate what's changed in each layer**

Files that have changed, been modified, added, or removed are indicated in the file tree. This can be adjusted to show changes for a specific layer, or aggregated changes up to this layer.

**Estimate "image efficiency"**

The lower left pane shows basic layer info and an experimental metric that will guess how much wasted space your image contains. This might be from duplicating files across layers, moving files across layers, or not fully removing files. Both a percentage "score" and total wasted file space is provided.

**Quick build/analysis cycles**

You can build a Docker image and do an immediate analysis with one command:
`dive build -t some-tag .`

You only need to replace your `docker build` command with the same `dive build`
command.

**CI Integration**

Analyze an image and get a pass/fail result based on the image efficiency and wasted space. Simply set `CI=true` in the environment when invoking any valid dive command.

**Multiple Image Sources and Container Engines Supported**

With the `--source` option, you can select where to fetch the container image from:
```bash
dive <your-image> --source <source>
```
or
```bash
dive <source>://<your-image>
```

With valid `source` options as such:
- `docker`: Docker engine (the default option)
- `docker-archive`: A Docker Tar Archive from disk
- `podman`: Podman engine (linux only)

## Installation

**Ubuntu/Debian**

Using debs:
```bash
DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -fOL "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb"
sudo apt install ./dive_${DIVE_VERSION}_linux_amd64.deb
```

Using snap:
```bash
sudo snap install docker
sudo snap install dive
sudo snap connect dive:docker-executables docker:docker-executables
sudo snap connect dive:docker-daemon docker:docker-daemon
```

> [!CAUTION]
> The Snap method is not recommended if you installed Docker via `apt-get`, since it might break your existing Docker daemon.
> 
> See also: https://github.com/wagoodman/dive/issues/546


**RHEL/Centos**
```bash
DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -fOL "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.rpm"
rpm -i dive_${DIVE_VERSION}_linux_amd64.rpm
```

**Arch Linux**

Available in the [extra repository](https://archlinux.org/packages/extra/x86_64/dive/) and can be installed via [pacman](https://wiki.archlinux.org/title/Pacman):

```bash
pacman -S dive
```

**Mac**

If you use [Homebrew](https://brew.sh):

```bash
brew install dive
```

If you use [MacPorts](https://www.macports.org):

```bash
sudo port install dive
```

Or download the latest Darwin build from the [releases page](https://github.com/wagoodman/dive/releases/latest).

**Windows**

If you use [Chocolatey](https://chocolatey.org)

```powershell
choco install dive
```

If you use [scoop](https://scoop.sh/)

```powershell
scoop install main/dive
```

If you use [winget](https://learn.microsoft.com/en-gb/windows/package-manager/):

```powershell
winget install --id wagoodman.dive
```

Or download the latest Windows build from the [releases page](https://github.com/wagoodman/dive/releases/latest).

**Go tools**
Requires Go version 1.10 or higher.

```bash
go install github.com/wagoodman/dive@latest
```
*Note*: installing in this way you will not see a proper version when running `dive -v`.

**Nix/NixOS**

On NixOS:
```bash
nix-env -iA nixos.dive
```
On non-NixOS (Linux, Mac)
```bash
nix-env -iA nixpkgs.dive
```

**X-CMD**

[x-cmd](https://www.x-cmd.com/) is a **toolbox for Posix Shell**, offering a lightweight package manager built using shell and awk.
```sh
x env use dive
```

**Docker**
```bash
docker pull docker.io/wagoodman/dive
# or alternatively
docker pull ghcr.io/wagoodman/dive
```

When running you'll need to include the Docker socket file:
```bash
docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    docker.io/wagoodman/dive:latest <dive arguments...>
```

Docker for Windows (showing PowerShell compatible line breaks; collapse to a single line for Command Prompt compatibility)
```bash
docker run --rm -it `
    -v /var/run/docker.sock:/var/run/docker.sock `
    docker.io/wagoodman/dive:latest <dive arguments...>
```

**Note:** depending on the version of docker you are running locally you may need to specify the docker API version as an environment variable:
```bash
   DOCKER_API_VERSION=1.37 dive ...
```
or if you are running with a docker image:
```bash
docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -e DOCKER_API_VERSION=1.37 \
    docker.io/wagoodman/dive:latest <dive arguments...>
```
if you are using an alternative runtime (Colima etc) then you may need to specify the docker host as an environment variable in order to pull local images:
```bash
   export DOCKER_HOST=$(docker context inspect -f '{{ .Endpoints.docker.Host }}')
```

## CI Integration

When running dive with the environment variable `CI=true` then the dive UI will be bypassed and will instead analyze your docker image, giving it a pass/fail indication via return code. Currently there are three metrics supported via a `.dive-ci` file that you can put at the root of your repo:
```
rules:
  # If the efficiency is measured below X%, mark as failed.
  # Expressed as a ratio between 0-1.
  lowestEfficiency: 0.95

  # If the amount of wasted space is at least X or larger than X, mark as failed.
  # Expressed in B, KB, MB, and GB.
  highestWastedBytes: 20MB

  # If the amount of wasted space makes up for X% or more of the image, mark as failed.
  # Note: the base image layer is NOT included in the total image size.
  # Expressed as a ratio between 0-1; fails if the threshold is met or crossed.
  highestUserWastedPercent: 0.20
```
You can override the CI config path with the `--ci-config` option.

## KeyBindings

Key Binding                                | Description
-------------------------------------------|---------------------------------------------------------
<kbd>Ctrl + C</kbd> or <kbd>Q</kbd>        | Exit
<kbd>Tab</kbd>                             | Switch between the layer and filetree views
<kbd>Ctrl + F</kbd>                        | Filter files
<kbd>ESC</kbd>                             | Close filter files
<kbd>PageUp</kbd> or <kbd>U</kbd>          | Scroll up a page
<kbd>PageDown</kbd> or <kbd>D</kbd>        | Scroll down a page
<kbd>Up</kbd> or <kbd>K</kbd>              | Move up one line within a page
<kbd>Down</kbd> or <kbd>J</kbd>            | Move down one line within a page
<kbd>Ctrl + A</kbd>                        | Layer view: see aggregated image modifications
<kbd>Ctrl + L</kbd>                        | Layer view: see current layer modifications
<kbd>Space</kbd>                           | Filetree view: collapse/uncollapse a directory
<kbd>Ctrl + Space</kbd>                    | Filetree view: collapse/uncollapse all directories
<kbd>Ctrl + A</kbd>                        | Filetree view: show/hide added files
<kbd>Ctrl + R</kbd>                        | Filetree view: show/hide removed files
<kbd>Ctrl + M</kbd>                        | Filetree view: show/hide modified files
<kbd>Ctrl + U</kbd>                        | Filetree view: show/hide unmodified files
<kbd>Ctrl + B</kbd>                        | Filetree view: show/hide file attributes
<kbd>PageUp</kbd> or <kbd>U</kbd>          | Filetree view: scroll up a page
<kbd>PageDown</kbd> or <kbd>D</kbd>        | Filetree view: scroll down a page

## UI Configuration

No configuration is necessary, however, you can create a config file and override values:
```yaml
# supported options are "docker" and "podman"
container-engine: docker
# continue with analysis even if there are errors parsing the image archive
ignore-errors: false
log:
  enabled: true
  path: ./dive.log
  level: info

# Note: you can specify multiple bindings by separating values with a comma.
# Note: UI hinting is derived from the first binding
keybinding:
  # Global bindings
  quit: ctrl+c
  toggle-view: tab
  filter-files: ctrl+f, ctrl+slash
  close-filter-files: esc
  up: up,k
  down: down,j
  left: left,h
  right: right,l

  # Layer view specific bindings
  compare-all: ctrl+a
  compare-layer: ctrl+l

  # File view specific bindings
  toggle-collapse-dir: space
  toggle-collapse-all-dir: ctrl+space
  toggle-added-files: ctrl+a
  toggle-removed-files: ctrl+r
  toggle-modified-files: ctrl+m
  toggle-unmodified-files: ctrl+u
  toggle-filetree-attributes: ctrl+b
  page-up: pgup,u
  page-down: pgdn,d

diff:
  # You can change the default files shown in the filetree (right pane). All diff types are shown by default.
  hide:
    - added
    - removed
    - modified
    - unmodified

filetree:
  # The default directory-collapse state
  collapse-dir: false

  # The percentage of screen width the filetree should take on the screen (must be >0 and <1)
  pane-width: 0.5

  # Show the file attributes next to the filetree
  show-attributes: true

layer:
  # Enable showing all changes from this layer and every previous layer
  show-aggregated-changes: false

```

dive will search for configs in the following locations:
- `$XDG_CONFIG_HOME/dive/*.yaml`
- `$XDG_CONFIG_DIRS/dive/*.yaml`
- `~/.config/dive/*.yaml`
- `~/.dive.yaml`

`.yml` can be used instead of `.yaml` if desired.


================================================
FILE: RELEASE.md
================================================
# Release process


## Creating a release

**Trigger a new release with `make release`**. 

At this point you'll see a preview changelog in the terminal. If you're happy with the 
changelog, press `y` to continue, otherwise you can abort and adjust the labels on the 
PRs and issues to be included in the release and re-run the release trigger command.


## Retracting a release

If a release is found to be problematic, it can be retracted with the following steps:

- Deleting the GitHub Release
- Untag the docker images in the `docker.io` registry
- Revert the brew formula in [`wagoodman/homebrew-dive`](https://github.com/wagoodman/homebrew-dive) to point to the previous release
- Add a new `retract` entry in the go.mod for the versioned release

**Note**: do not delete release tags from the git repository since there may already be references to the release
in the go proxy, which will cause confusion when trying to reuse the tag later (the H1 hash will not match and there
will be a warning when users try to pull the new release).


================================================
FILE: Taskfile.yaml
================================================

version: "3"
vars:
  OWNER: wagoodman
  PROJECT: dive

  # static file dirs
  TOOL_DIR: .tool
  TMP_DIR: .tmp

  # TOOLS
  BINNY: "{{ .TOOL_DIR }}/binny"
  CHRONICLE: "{{ .TOOL_DIR }}/chronicle"
  GORELEASER: "{{ .TOOL_DIR }}/goreleaser"
  GOLANGCI_LINT: "{{ .TOOL_DIR }}/golangci-lint"
  TASK: "{{ .TOOL_DIR }}/task"
  BOUNCER: "{{ .TOOL_DIR }}/bouncer"
  GLOW: "{{ .TOOL_DIR }}/glow"

  # used for changelog generation
  CHANGELOG: CHANGELOG.md
  NEXT_VERSION: VERSION

  # note: the snapshot dir must be a relative path starting with ./
  SNAPSHOT_DIR: ./snapshot
  SNAPSHOT_CMD: "{{ .GORELEASER }} release --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --skip=publish --skip=sign"
  BUILD_CMD:    "{{ .GORELEASER }} build   --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --single-target"
  RELEASE_CMD:  "{{ .GORELEASER }} release --clean --release-notes {{ .CHANGELOG }}"
  VERSION:
    sh: git describe --dirty --always --tags

  # used for acceptance tests
  TEST_IMAGE: busybox:latest
  DOCKER_CLI_VERSION: 28.0.0

env:
  GNUMAKEFLAGS: '--no-print-directory'
  DOCKER_CLI_VERSION: "{{ .DOCKER_CLI_VERSION }}"

tasks:

  ## High-level tasks #################################

  default:
    desc: Run all validation tasks
    aliases:
      - pr-validations
      - validations
    cmds:
      - task: static-analysis
      - task: test

  static-analysis:
    desc: Run all static analysis tasks
    cmds:
      - task: check-go-mod-tidy
      - task: check-licenses
      - task: lint

  test:
    desc: Run all levels of test
    cmds:
      - task: unit
      - task: cli

  ## Bootstrap tasks #################################

  binny:
    internal: true
    # desc: Get the binny tool
    generates:
      - "{{ .BINNY }}"
    status:
      - "test -f {{ .BINNY }}"
    cmd: "curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh -s -- -b .tool"
    silent: true

  tools:
    desc: Install all tools needed for CI and local development
    deps: [binny]
    aliases:
      - bootstrap
    generates:
      - ".binny.yaml"
      - "{{ .TOOL_DIR }}/*"
    status:
      - "{{ .BINNY }} check -v"
    cmd: "{{ .BINNY }} install -v"
    silent: true

  update-tools:
    desc: Update pinned versions of all tools to their latest available versions
    deps: [binny]
    generates:
      - ".binny.yaml"
      - "{{ .TOOL_DIR }}/*"
    cmd: "{{ .BINNY }} update -v"
    silent: true

  list-tools:
    desc: List all tools needed for CI and local development
    deps: [binny]
    cmd: "{{ .BINNY }} list"
    silent: true

  list-tool-updates:
    desc: List all tools that are not up to date relative to the binny config
    deps: [binny]
    cmd: "{{ .BINNY }} list --updates"
    silent: true

  tmpdir:
    silent: true
    generates:
      - "{{ .TMP_DIR }}"
    cmd: "mkdir -p {{ .TMP_DIR }}"

  ## Static analysis tasks #################################

  format:
    desc: Auto-format all source code
    deps: [tools]
    cmds:
      - gofmt -w -s .
      - go mod tidy

  lint-fix:
    desc: Auto-format all source code + run golangci lint fixers
    deps: [tools]
    cmds:
      - task: format
      - "{{ .GOLANGCI_LINT }} run --tests=false --fix"

  lint:
    desc: Run gofmt + golangci lint checks
    vars:
      BAD_FMT_FILES:
        sh: gofmt -l -s .
      BAD_FILE_NAMES:
        sh: "find . | grep -e ':' || true"
    deps: [tools]
    cmds:
      # ensure there are no go fmt differences
      - cmd: 'test -z "{{ .BAD_FMT_FILES }}" || (echo "files with gofmt issues: [{{ .BAD_FMT_FILES }}]"; exit 1)'
        silent: true
      # ensure there are no files with ":" in it (a known back case in the go ecosystem)
      - cmd: 'test -z "{{ .BAD_FILE_NAMES }}" || (echo "files with bad names: [{{ .BAD_FILE_NAMES }}]"; exit 1)'
        silent: true
      # run linting
      - "{{ .GOLANGCI_LINT }} run --tests=false"

  check-licenses:
    # desc: Ensure transitive dependencies are compliant with the current license policy
    deps: [tools]
    cmd: "{{ .BOUNCER }} check ./..."

  check-go-mod-tidy:
    # desc: Ensure go.mod and go.sum are up to date
    cmds:
      - cmd: |
          if ! go mod tidy -diff; then
            echo "go.mod and/or go.sum need updates. Please run 'go mod tidy'"
            exit 1
          fi
    silent: true


  ## Testing tasks #################################

  unit:
    desc: Run unit tests
    deps:
      - tmpdir
    vars:
      TEST_PKGS:
        sh: "go list ./... | grep -v '^github.com/wagoodman/dive/cmd/dive/cli$'  | tr '\n' ' '"

      # unit test coverage threshold (in % coverage)
      COVERAGE_THRESHOLD: 25
    cmds:
      - "go test -coverprofile {{ .TMP_DIR }}/unit-coverage-details.txt {{ .TEST_PKGS }}"
      - cmd: ".github/scripts/coverage.py {{ .COVERAGE_THRESHOLD }} {{ .TMP_DIR }}/unit-coverage-details.txt"
        silent: true

  cli:
    desc: Run CLI tests
    cmds:
      - "go test github.com/wagoodman/dive/cmd/dive/cli -v"

  ## Acceptance tests #################################

  ci-test-linux:
    cmds:
      - task: ci-test-linux-run
      - task: ci-test-docker-image
      - task: ci-test-deb-package-install
      - task: ci-test-rpm-package-install

  ci-test-docker-image:
    desc: Test using the docker image
    cmds:
      - |
        docker run \
          --rm \
          -t \
          --env CLICOLOR_FORCE=true \
          -v /var/run/docker.sock:/var/run/docker.sock \
          'docker.io/wagoodman/dive:latest' \
            '{{ .TEST_IMAGE }}' \
            --ci

  ci-test-deb-package-install:
    desc: Test debian package installation
    cmds:
      - |
        docker run \
          --platform linux/amd64 \
          -v /var/run/docker.sock:/var/run/docker.sock \
          -v /${PWD}:/src \
          -w /src \
          --env CLICOLOR_FORCE=true \
          ubuntu:latest \
            /bin/bash -x -c "\
              apt update && \
              apt install -y curl && \
              curl -L 'https://download.docker.com/linux/static/stable/x86_64/docker-{{ .DOCKER_CLI_VERSION }}.tgz' | \
                tar -vxzf - docker/docker --strip-component=1 && \
                mv docker /usr/local/bin/ &&\
              docker version && \
              apt install ./snapshot/dive_*_linux_amd64.deb -y && \
              dive --version && \
              dive '{{ .TEST_IMAGE }}' --ci \
            "

  ci-test-rpm-package-install:
    desc: Test RPM package installation
    cmds:
      - |
        docker run \
          --platform linux/amd64 \
          -v /var/run/docker.sock:/var/run/docker.sock \
          -v /${PWD}:/src \
          -w /src \
          --env CLICOLOR_FORCE=true \
          fedora:latest \
            /bin/bash -x -c "\
              curl -L 'https://download.docker.com/linux/static/stable/x86_64/docker-{{ .DOCKER_CLI_VERSION }}.tgz' | \
                tar -vxzf - docker/docker --strip-component=1 && \
                mv docker /usr/local/bin/ &&\
              docker version && \
              dnf install ./snapshot/dive_*_linux_amd64.rpm -y && \
              dive --version && \
              dive '{{ .TEST_IMAGE }}' --ci \
            "

  generate-compressed-test-images:
    desc: Generate compressed test images for testing
    cmds:
      - |
        for alg in uncompressed gzip estargz zstd; do \
          for exporter in docker image; do \
            docker buildx build \
              -f .data/Dockerfile.minimal \
              --tag test-dive-${exporter}:${alg} \
              --output type=${exporter},force-compression=true,compression=${alg} . ; \
          done ; \
        done && \
        echo 'Exported test data!'

  generate-compressed-test-data:
    desc: Generate compressed test data for testing
    cmds:
      - |
        for alg in uncompressed gzip estargz zstd; \
        do \
          docker buildx build \
            -f .data/Dockerfile.minimal \
            --output type=tar,dest=.data/test-${alg}-image.tar,force-compression=true,compression=${alg} . ; \
          docker buildx build \
            -f .data/Dockerfile.minimal \
            --output type=oci,dest=.data/test-oci-${alg}-image.tar,force-compression=true,compression=${alg} . ; \
        done && \
        echo 'Exported test data!'

  ci-test-linux-run:
    desc: Test Linux binary execution (CI only)
    deps: [ci-check, generate-compressed-test-images]
    cmds:
      - |
        ls -la {{ .SNAPSHOT_DIR }}
        ls -la {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1
        chmod 755 {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive && \
        {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive '{{ .TEST_IMAGE }}' --ci && \
        {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive --source docker-archive .data/test-kaniko-image.tar --ci --ci-config .data/.dive-ci
      - |
        for alg in uncompressed gzip estargz zstd; do \
          for exporter in docker image; do \
            {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive "test-dive-${exporter}:${alg}" --ci ; \
          done && \
          {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive --source docker-archive .data/test-oci-${alg}-image.tar --ci --ci-config .data/.dive-ci; \
        done

  ci-test-mac-run:
    desc: Test macOS binary execution (CI only)
    deps: [ci-check]
    cmds:
      - |
        chmod 755 {{ .SNAPSHOT_DIR }}/dive_darwin_amd64_v1/dive && \
        {{ .SNAPSHOT_DIR }}/dive_darwin_amd64_v1/dive --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci


  ## Build-related targets #################################

  build:
    desc: Build the project
    deps: [tools, tmpdir]
    generates:
      - "{{ .PROJECT }}"
    cmds:
      - silent: true
        cmd: |
          echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml
          cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml

      - "{{ .BUILD_CMD }}"

  snapshot:
    desc: Create a snapshot release
    aliases:
      - build
    deps: [tools, tmpdir]
    sources:
      - "**/*.go"
      - ".goreleaser.yaml"
    method: checksum
    cmds:
      - silent: true
        cmd: |
          echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml
          cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml

      - "{{ .SNAPSHOT_CMD }}"

  changelog:
    desc: Generate a changelog
    deps: [tools]
    generates:
      - "{{ .CHANGELOG }}"
      - "{{ .NEXT_VERSION }}"
    cmds:
      - "{{ .CHRONICLE }} -vv -n --version-file {{ .NEXT_VERSION }} > {{ .CHANGELOG }}"
      - "{{ .GLOW }} -w 200 {{ .CHANGELOG }}"


  ## Release targets #################################

  release:
    desc: Create a release
    interactive: true
    deps: [tools]
    cmds:
      - cmd: .github/scripts/trigger-release.sh
        silent: true


  ## CI-only targets #################################

  ci-check:
    preconditions:
      - sh: test -n "$CI"
        msg: "This step should ONLY be run in CI. Exiting..."
    cmds:
      - echo "Running in CI environment"
    silent: true
    internal: true

  ci-release:
    # desc: "[CI only] Create a release"
    deps: [ci-check, tools]
    cmds:
      - "{{ .CHRONICLE }} -vvv > CHANGELOG.md"
      - cmd: "cat CHANGELOG.md"
        silent: true
      - "{{ .RELEASE_CMD }}"


  ## Cleanup targets #################################

  clean-snapshot:
    desc: Remove any snapshot builds
    cmds:
      - "rm -rf {{ .SNAPSHOT_DIR }}"
      - "rm -rf {{ .TMP_DIR }}/goreleaser.yaml"


================================================
FILE: cmd/dive/cli/cli.go
================================================
package cli

import (
	"github.com/anchore/clio"
	"github.com/spf13/cobra"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/command"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/ui"
	"github.com/wagoodman/dive/internal/bus"
	"github.com/wagoodman/dive/internal/log"
)

func Application(id clio.Identification) clio.Application {
	app, _ := create(id)
	return app
}

func Command(id clio.Identification) *cobra.Command {
	_, cmd := create(id)
	return cmd
}

func create(id clio.Identification) (clio.Application, *cobra.Command) {
	clioCfg := clio.NewSetupConfig(id).
		WithGlobalConfigFlag().   // add persistent -c <path> for reading an application config from
		WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
		WithConfigInRootHelp().   // --help on the root command renders the full application config in the help text
		WithUI(ui.None()).
		WithInitializers(
			func(state *clio.State) error {
				bus.Set(state.Bus)
				log.Set(state.Logger)

				//stereoscope.SetBus(state.Bus)
				//stereoscope.SetLogger(state.Logger)
				return nil
			},
		)
	//WithPostRuns(func(_ *clio.State, _ error) {
	//	stereoscope.Cleanup()
	//})

	app := clio.New(*clioCfg)

	rootCmd := command.Root(app)

	rootCmd.AddCommand(
		clio.VersionCommand(id),
		clio.ConfigCommand(app, nil),
		command.Build(app),
	)

	return app, rootCmd
}


================================================
FILE: cmd/dive/cli/cli_build_test.go
================================================
package cli

import (
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"regexp"
	"testing"
)

func Test_Build_Dockerfile(t *testing.T) {
	t.Setenv("DIVE_CONFIG", "./testdata/image-multi-layer-dockerfile/dive-pass.yaml")

	t.Run("implicit dockerfile", func(t *testing.T) {
		rootCmd := getTestCommand(t, "build testdata/image-multi-layer-dockerfile")
		stdout := Capture().WithStdout().WithSuppress().Run(t, func() {
			require.NoError(t, rootCmd.Execute())
		})
		snaps.MatchSnapshot(t, stdout)
	})

	t.Run("explicit file flag", func(t *testing.T) {
		rootCmd := getTestCommand(t, "build testdata/image-multi-layer-dockerfile -f testdata/image-multi-layer-dockerfile/Dockerfile")
		stdout := Capture().WithStdout().WithSuppress().Run(t, func() {
			require.NoError(t, rootCmd.Execute())
		})
		snaps.MatchSnapshot(t, stdout)
	})
}

func Test_Build_Containerfile(t *testing.T) {
	t.Setenv("DIVE_CONFIG", "./testdata/image-multi-layer-containerfile/dive-pass.yaml")

	t.Run("implicit containerfile", func(t *testing.T) {
		rootCmd := getTestCommand(t, "build testdata/image-multi-layer-containerfile")
		stdout := Capture().WithStdout().WithSuppress().Run(t, func() {
			require.NoError(t, rootCmd.Execute())
		})
		snaps.MatchSnapshot(t, stdout)
	})

	t.Run("explicit file flag", func(t *testing.T) {
		rootCmd := getTestCommand(t, "build testdata/image-multi-layer-containerfile -f testdata/image-multi-layer-containerfile/Containerfile")
		stdout := Capture().WithStdout().WithSuppress().Run(t, func() {
			require.NoError(t, rootCmd.Execute())
		})
		snaps.MatchSnapshot(t, stdout)
	})
}

func Test_Build_CI_gate_fail(t *testing.T) {
	t.Setenv("DIVE_CONFIG", "./testdata/image-multi-layer-dockerfile/dive-fail.yaml")

	rootCmd := getTestCommand(t, "build testdata/image-multi-layer-dockerfile")
	stdout := Capture().WithStdout().WithSuppress().Run(t, func() {
		// failing gate should result in a non-zero exit code
		require.Error(t, rootCmd.Execute())
	})
	snaps.MatchSnapshot(t, stdout)

}

func Test_BuildFailure(t *testing.T) {

	t.Run("nonexistent directory", func(t *testing.T) {
		rootCmd := getTestCommand(t, "build ./path/does/not/exist")
		combined := Capture().WithStdout().WithStderr().Run(t, func() {
			require.ErrorContains(t, rootCmd.Execute(), "could not find Containerfile or Dockerfile")
		})

		assert.Contains(t, combined, "Building image")

		snaps.MatchSnapshot(t, combined)
	})

	t.Run("invalid dockerfile", func(t *testing.T) {
		rootCmd := getTestCommand(t, "build ./testdata/invalid")
		combined := Capture().WithStdout().WithStderr().WithSuppress().Run(t, func() {

			require.ErrorContains(t, rootCmd.Execute(), "cannot build image: exit status 1")
		})

		assert.Contains(t, combined, "Building image")
		// ensure we're passing through docker feedback
		assert.Contains(t, combined, "unknown instruction: INVALID")

		// replace anything starting with "docker-desktop://", like "docker-desktop://dashboard/build/desktop-linux/desktop-linux/ujdmhgkwo0sqqpopsnum3xakd"
		combined = regexp.MustCompile("docker-desktop://[^ ]+").ReplaceAllString(combined, "docker-desktop://<redacted>")

		snaps.MatchSnapshot(t, combined)
	})
}


================================================
FILE: cmd/dive/cli/cli_ci_test.go
================================================
package cli

import (
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"testing"
)

func Test_CI_DefaultCIConfig(t *testing.T) {
	// this lets the test harness to unset any DIVE_CONFIG env var
	t.Setenv("DIVE_CONFIG", "-")

	rootCmd := getTestCommand(t, repoPath(t, ".data/test-docker-image.tar")+" -vv")
	cd(t, "testdata/default-ci-config")
	combined := Capture().WithStdout().WithStderr().Run(t, func() {
		// failing gate should result in a non-zero exit code
		require.Error(t, rootCmd.Execute())
	})

	assert.Contains(t, combined, "lowest-efficiency: \"0.96\"", "missing lowest-efficiency rule")
	assert.Contains(t, combined, "highest-wasted-bytes: 19Mb", "missing highest-wasted-bytes rule")
	assert.Contains(t, combined, "highest-user-wasted-percent: \"0.6\"", "missing highest-user-wasted-percent rule")

	snaps.MatchSnapshot(t, combined)
}

func Test_CI_Fail(t *testing.T) {
	t.Setenv("DIVE_CONFIG", "./testdata/image-multi-layer-dockerfile/dive-fail.yaml")

	rootCmd := getTestCommand(t, "build testdata/image-multi-layer-dockerfile")
	stdout := Capture().WithStdout().WithSuppress().Run(t, func() {
		// failing gate should result in a non-zero exit code
		require.Error(t, rootCmd.Execute())
	})
	snaps.MatchSnapshot(t, stdout)

}

func Test_CI_LegacyRules(t *testing.T) {
	t.Setenv("DIVE_CONFIG", "./testdata/config/dive-ci-legacy.yaml")

	rootCmd := getTestCommand(t, "config --load")
	all := Capture().All().Run(t, func() {
		require.NoError(t, rootCmd.Execute())
	})

	// this proves that we can load the legacy rules and map them to the standard rules
	assert.Contains(t, all, "lowest-efficiency: '0.95'", "missing lowest-efficiency legacy rule")
	assert.Contains(t, all, "highest-wasted-bytes: '20MB'", "missing highest-wasted-bytes legacy rule")
	assert.Contains(t, all, "highest-user-wasted-percent: '0.2'", "missing highest-user-wasted-percent legacy rule")
}


================================================
FILE: cmd/dive/cli/cli_config_test.go
================================================
package cli

import (
	"github.com/stretchr/testify/require"
	"testing"
)

func Test_Config(t *testing.T) {
	t.Setenv("DIVE_CONFIG", "./testdata/image-multi-layer-dockerfile/dive-pass.yaml")

	rootCmd := getTestCommand(t, "config --load")
	all := Capture().All().Run(t, func() {
		require.NoError(t, rootCmd.Execute())
	})

	snaps.MatchSnapshot(t, all)
}


================================================
FILE: cmd/dive/cli/cli_json_test.go
================================================
package cli

import (
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"os"
	"path/filepath"
	"testing"
)

func Test_JsonOutput(t *testing.T) {

	t.Run("json output", func(t *testing.T) {
		dest := t.TempDir()
		file := filepath.Join(dest, "output.json")
		rootCmd := getTestCommand(t, "busybox:1.37.0@sha256:ad9fa4d07136a83e69a54ef00102f579d04eba431932de3b0f098cc5d5948f9f --json "+file)
		combined := Capture().WithStdout().WithStderr().Run(t, func() {
			require.NoError(t, rootCmd.Execute())
		})

		assert.Contains(t, combined, "Exporting details")
		assert.Contains(t, combined, "file")

		contents, err := os.ReadFile(file)
		require.NoError(t, err)

		snaps.MatchJSON(t, contents)
	})
}


================================================
FILE: cmd/dive/cli/cli_load_test.go
================================================
package cli

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"os"
	"os/exec"
	"testing"
)

func Test_LoadImage(t *testing.T) {
	image := "busybox:1.37.0@sha256:ad9fa4d07136a83e69a54ef00102f579d04eba431932de3b0f098cc5d5948f9f"
	archive := repoPath(t, ".data/test-docker-image.tar")

	t.Run("from docker engine", func(t *testing.T) {
		runWithCombinedOutput(t, fmt.Sprintf("docker://%s", image))
	})

	t.Run("from docker engine (flag)", func(t *testing.T) {

		runWithCombinedOutput(t, fmt.Sprintf("--source docker %s", image))
	})

	t.Run("from podman engine", func(t *testing.T) {
		if _, err := exec.LookPath("podman"); err != nil {
			t.Skip("podman not installed, skipping test")
		}
		// pull the image from podman first
		require.NoError(t, exec.Command("podman", "pull", image).Run())

		runWithCombinedOutput(t, fmt.Sprintf("podman://%s", image))
	})

	t.Run("from podman engine (flag)", func(t *testing.T) {
		if _, err := exec.LookPath("podman"); err != nil {
			t.Skip("podman not installed, skipping test")
		}

		// pull the image from podman first
		require.NoError(t, exec.Command("podman", "pull", image).Run())

		runWithCombinedOutput(t, fmt.Sprintf("--source podman %s", image))
	})

	t.Run("from archive", func(t *testing.T) {
		runWithCombinedOutput(t, fmt.Sprintf("docker-archive://%s", archive))
	})

	t.Run("from archive (flag)", func(t *testing.T) {
		runWithCombinedOutput(t, fmt.Sprintf("--source docker-archive %s", archive))
	})
}

func runWithCombinedOutput(t testing.TB, cmd string) {
	t.Helper()
	rootCmd := getTestCommand(t, cmd)
	combined := Capture().WithStdout().WithStderr().Run(t, func() {
		require.NoError(t, rootCmd.Execute())
	})

	assertLoadOutput(t, combined)
}

func assertLoadOutput(t testing.TB, combined string) {
	t.Helper()
	assert.Contains(t, combined, "Loading image")
	assert.Contains(t, combined, "Analyzing image")
	assert.Contains(t, combined, "Evaluating image")
	snaps.MatchSnapshot(t, combined)
}

func Test_FetchFailure(t *testing.T) {
	t.Run("nonexistent image", func(t *testing.T) {
		rootCmd := getTestCommand(t, "docker:wagoodman/nonexistent/image:tag")
		combined := Capture().WithStdout().WithStderr().Run(t, func() {
			require.ErrorContains(t, rootCmd.Execute(), "cannot load image: Error response from daemon: invalid reference format")
		})

		assert.Contains(t, combined, "Loading image")

		snaps.MatchSnapshot(t, combined)
	})

	t.Run("invalid image name", func(t *testing.T) {
		rootCmd := getTestCommand(t, "docker:///wagoodman/invalid:image:format")
		combined := Capture().WithStdout().WithStderr().Run(t, func() {
			require.ErrorContains(t, rootCmd.Execute(), "cannot load image: Error response from daemon: invalid reference format")
		})

		assert.Contains(t, combined, "Loading image")

		snaps.MatchSnapshot(t, combined)
	})
}

func cd(t testing.TB, to string) {
	t.Helper()
	from, err := os.Getwd()
	require.NoError(t, err)
	require.NoError(t, os.Chdir(to))
	t.Cleanup(func() {
		require.NoError(t, os.Chdir(from))
	})
}


================================================
FILE: cmd/dive/cli/cli_test.go
================================================
package cli

import (
	"bytes"
	"flag"
	"github.com/anchore/clio"
	"github.com/charmbracelet/lipgloss"
	snapsPkg "github.com/gkampitakis/go-snaps/snaps"
	"github.com/google/shlex"
	"github.com/muesli/termenv"
	"github.com/spf13/cobra"
	"github.com/stretchr/testify/require"
	"go.uber.org/atomic"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

var (
	updateSnapshot = flag.Bool("update", false, "update any test snapshots")
	snaps          *snapsPkg.Config
	repoRootCache  atomic.String
)

func TestMain(m *testing.M) {
	// flags are not parsed until after test.Main is called...
	flag.Parse()

	os.Unsetenv("DIVE_CONFIG")

	// disable colors
	lipgloss.SetColorProfile(termenv.Ascii)

	snaps = snapsPkg.WithConfig(
		snapsPkg.Update(*updateSnapshot),
		snapsPkg.Dir("testdata/snapshots"),
	)

	v := m.Run()

	snapsPkg.Clean(m)

	os.Exit(v)
}

func TestUpdateSnapshotDisabled(t *testing.T) {
	require.False(t, *updateSnapshot, "update snapshot flag should be disabled")
}

func repoPath(t testing.TB, path string) string {
	t.Helper()
	root := repoRoot(t)
	return filepath.Join(root, path)
}

func repoRoot(t testing.TB) string {
	val := repoRootCache.Load()
	if val != "" {
		return val
	}
	t.Helper()
	// use git to find the root of the repo
	out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
	if err != nil {
		t.Fatalf("failed to get repo root: %v", err)
	}
	val = strings.TrimSpace(string(out))
	repoRootCache.Store(val)
	return val
}

func getTestCommand(t testing.TB, cmd string) *cobra.Command {
	switch os.Getenv("DIVE_CONFIG") {
	case "":
		t.Setenv("DIVE_CONFIG", "./testdata/dive-enable-ci.yaml")
	case "-":
		t.Setenv("DIVE_CONFIG", "")
	}

	// need basic output to logger for testing...
	//l, err := logrus.New(logrus.DefaultConfig())
	//require.NoError(t, err)
	//log.Set(l)

	// get the root command
	c := Command(clio.Identification{
		Name:    "dive",
		Version: "testing",
	})

	args, err := shlex.Split(cmd)
	require.NoError(t, err, "failed to parse command line %q", cmd)

	c.SetArgs(args)

	return c
}

type capturer struct {
	stdout   bool
	stderr   bool
	suppress bool
}

func Capture() *capturer {
	return &capturer{}
}

func (c *capturer) WithSuppress() *capturer {
	c.suppress = true
	return c
}

func (c *capturer) All() *capturer {
	c.stdout = true
	c.stderr = true
	return c
}

func (c *capturer) WithStdout() *capturer {
	c.stdout = true
	return c
}

func (c *capturer) WithStderr() *capturer {
	c.stderr = true
	return c
}

func (c *capturer) Run(t testing.TB, f func()) string {
	t.Helper()

	r, w, err := os.Pipe()
	if err != nil {
		panic(err)
	}

	devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
	if err != nil {
		panic(err)
	}
	defer devNull.Close()

	oldStdout := os.Stdout
	oldStderr := os.Stderr

	if c.stdout {
		os.Stdout = w
	} else if c.suppress {
		os.Stdout = devNull
	}

	if c.stderr {
		os.Stderr = w
	} else if c.suppress {
		os.Stderr = devNull
	}

	defer func() {
		os.Stdout = oldStdout
		os.Stderr = oldStderr
	}()

	f()
	require.NoError(t, w.Close())

	var buf bytes.Buffer
	_, err = io.Copy(&buf, r)
	require.NoError(t, err)

	return buf.String()
}


================================================
FILE: cmd/dive/cli/internal/command/adapter/analyzer.go
================================================
package adapter

import (
	"context"
	"fmt"
	"github.com/dustin/go-humanize"
	"github.com/wagoodman/dive/dive/image"
	"github.com/wagoodman/dive/internal/bus"
	"github.com/wagoodman/dive/internal/bus/event/payload"
	"github.com/wagoodman/dive/internal/log"
)

type Analyzer interface {
	Analyze(ctx context.Context, img *image.Image) (*image.Analysis, error)
}

type analysisActionObserver struct {
	Analyzer func(context.Context, *image.Image) (*image.Analysis, error)
}

func NewAnalyzer() Analyzer {
	return analysisActionObserver{
		Analyzer: image.Analyze,
	}
}

func (a analysisActionObserver) Analyze(ctx context.Context, img *image.Image) (*image.Analysis, error) {
	log.WithFields("image", img.Request).Infof("analyzing")

	layers := len(img.Layers)
	var files int
	var fileSize uint64
	for _, layer := range img.Layers {
		files += layer.Tree.Size
		fileSize += layer.Tree.FileSize
	}
	fileSizeStr := humanize.Bytes(fileSize)
	filesStr := humanize.Comma(int64(files))

	log.Debugf("├── layers: %d", layers)
	log.Debugf("├── files: %s", filesStr)
	log.Debugf("└── file size: %s", fileSizeStr)

	mon := bus.StartTask(payload.GenericTask{
		Title: payload.Title{
			Default:      "Analyzing image",
			WhileRunning: "Analyzing image",
			OnSuccess:    "Analyzed image",
		},
		HideOnSuccess:      false,
		HideStageOnSuccess: false,
		ID:                 img.Request,
		Context:            fmt.Sprintf("[layers:%d files:%s size:%s]", layers, filesStr, fileSizeStr),
	})

	analysis, err := a.Analyzer(ctx, img)
	if err != nil {
		mon.SetError(err)
	} else {
		mon.SetCompleted()
	}

	if err == nil && analysis == nil {
		err = fmt.Errorf("no results returned")
	}

	return analysis, err
}


================================================
FILE: cmd/dive/cli/internal/command/adapter/evaluator.go
================================================
package adapter

import (
	"context"
	"fmt"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/command/ci"
	"github.com/wagoodman/dive/dive/image"
	"github.com/wagoodman/dive/internal/bus"
	"github.com/wagoodman/dive/internal/bus/event/payload"
	"github.com/wagoodman/dive/internal/log"
)

type Evaluator interface {
	Evaluate(ctx context.Context, analysis *image.Analysis) ci.Evaluation
}

type evaluationActionObserver struct {
	ci.Evaluator
}

func NewEvaluator(rules []ci.Rule) Evaluator {
	return evaluationActionObserver{
		Evaluator: ci.NewEvaluator(rules),
	}
}

func (c evaluationActionObserver) Evaluate(ctx context.Context, analysis *image.Analysis) ci.Evaluation {
	log.WithFields("image", analysis.Image).Infof("evaluating image")
	mon := bus.StartTask(payload.GenericTask{
		Title: payload.Title{
			Default:      "Evaluating image",
			WhileRunning: "Evaluating image",
			OnSuccess:    "Evaluated image",
		},
		HideOnSuccess:      false,
		HideStageOnSuccess: false,
		ID:                 analysis.Image,
		Context:            fmt.Sprintf("[rules: %d]", len(c.Rules)),
	})
	eval := c.Evaluator.Evaluate(ctx, analysis)
	if eval.Pass {
		mon.SetCompleted()
	} else {
		mon.SetError(fmt.Errorf("failed evaluation"))
	}
	bus.Report(eval.Report)
	return eval
}


================================================
FILE: cmd/dive/cli/internal/command/adapter/exporter.go
================================================
package adapter

import (
	"context"
	"fmt"
	"github.com/spf13/afero"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/command/export"
	"github.com/wagoodman/dive/dive/image"
	"github.com/wagoodman/dive/internal/bus"
	"github.com/wagoodman/dive/internal/bus/event/payload"
	"github.com/wagoodman/dive/internal/log"
	"os"
)

type Exporter interface {
	ExportTo(ctx context.Context, img *image.Analysis, path string) error
}

type jsonExporter struct {
	filesystem afero.Fs
}

func NewExporter(fs afero.Fs) Exporter {
	return &jsonExporter{
		filesystem: fs,
	}
}

func (e *jsonExporter) ExportTo(ctx context.Context, analysis *image.Analysis, path string) error {
	log.WithFields("path", path).Infof("exporting analysis")

	mon := bus.StartTask(payload.GenericTask{
		Title: payload.Title{
			Default:      "Exporting details",
			WhileRunning: "Exporting details",
			OnSuccess:    "Exported details",
		},
		HideOnSuccess:      false,
		HideStageOnSuccess: false,
		ID:                 analysis.Image,
		Context:            fmt.Sprintf("[file: %s]", path),
	})

	bytes, err := export.NewExport(analysis).Marshal()
	if err != nil {
		mon.SetError(err)
		return fmt.Errorf("cannot marshal export payload: %w", err)
	} else {
		mon.SetCompleted()
	}

	file, err := e.filesystem.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("cannot open export file: %w", err)
	}
	defer file.Close()

	_, err = file.Write(bytes)
	if err != nil {
		return fmt.Errorf("cannot write to export file: %w", err)
	}
	return nil
}


================================================
FILE: cmd/dive/cli/internal/command/adapter/resolver.go
================================================
package adapter

import (
	"context"
	"github.com/wagoodman/dive/dive/image"
	"github.com/wagoodman/dive/internal/bus"
	"github.com/wagoodman/dive/internal/bus/event/payload"
	"github.com/wagoodman/dive/internal/log"
	"strings"
	"time"
)

type imageActionObserver struct {
	image.Resolver
}

func ImageResolver(resolver image.Resolver) image.Resolver {
	return imageActionObserver{
		Resolver: resolver,
	}
}

func (i imageActionObserver) Build(ctx context.Context, options []string) (*image.Image, error) {
	log.Info("building image")
	log.Debugf("└── %s", strings.Join(options, " "))

	mon := bus.StartTask(payload.GenericTask{
		Title: payload.Title{
			Default:      "Building image",
			WhileRunning: "Building image",
			OnSuccess:    "Built image",
		},
		HideOnSuccess:      false,
		HideStageOnSuccess: false,
		Context:            "... " + strings.Join(options, " "),
	})

	ctx = payload.SetGenericProgressToContext(ctx, mon)

	img, err := i.Resolver.Build(ctx, options)
	if err != nil {
		mon.SetError(err)
	} else {
		mon.SetCompleted()
	}
	return img, err
}

func (i imageActionObserver) Fetch(ctx context.Context, id string) (*image.Image, error) {
	log.WithFields("image", id).Info("fetching")
	log.Debugf("└── resolver: %s", i.Resolver.Name())

	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	mon := bus.StartTask(payload.GenericTask{
		Title: payload.Title{
			Default:      "Loading image",
			WhileRunning: "Loading image",
			OnSuccess:    "Fetched image",
		},
		HideOnSuccess:      false,
		HideStageOnSuccess: false,
		ID:                 id,
		Context:            id,
	})

	ctx = payload.SetGenericProgressToContext(ctx, mon)

	go func() {
		// in 5 seconds if the context is not cancelled, log the message
		select { // nolint:gosimple
		case <-time.After(3 * time.Second):
			if ctx.Err() == nil {
				bus.Notify(" • this can take a while for large images...")
				mon.AtomicStage.Set("(this can take a while for large images)")

				// TODO: default level should be error for this to work when using the UI
				//log.Warn("this can take a while for large images")
			}
		}
	}()

	img, err := i.Resolver.Fetch(ctx, id)
	if err != nil {
		mon.SetError(err)
	} else {
		mon.SetCompleted()
	}
	return img, err
}


================================================
FILE: cmd/dive/cli/internal/command/build.go
================================================
package command

import (
	"fmt"
	"github.com/anchore/clio"
	"github.com/spf13/cobra"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/command/adapter"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/options"
	"github.com/wagoodman/dive/dive"
)

type buildOptions struct {
	options.Application `yaml:",inline" mapstructure:",squash"`

	// reserved for future use of build-only flags
}

func Build(app clio.Application) *cobra.Command {
	opts := &buildOptions{
		Application: options.DefaultApplication(),
	}
	return app.SetupCommand(&cobra.Command{
		Use:                "build [any valid `docker build` arguments]",
		Short:              "Builds and analyzes a docker image from a Dockerfile (this is a thin wrapper for the `docker build` command).",
		DisableFlagParsing: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := setUI(app, opts.Application); err != nil {
				return fmt.Errorf("failed to set UI: %w", err)
			}

			resolver, err := dive.GetImageResolver(opts.Analysis.Source)
			if err != nil {
				return fmt.Errorf("cannot determine image provider for build: %w", err)
			}

			ctx := cmd.Context()

			img, err := adapter.ImageResolver(resolver).Build(ctx, args)
			if err != nil {
				return fmt.Errorf("cannot build image: %w", err)
			}

			return run(cmd.Context(), opts.Application, img, resolver)
		},
	}, opts)
}


================================================
FILE: cmd/dive/cli/internal/command/ci/evaluator.go
================================================
package ci

import (
	"fmt"
	"github.com/charmbracelet/lipgloss"
	"golang.org/x/net/context"
	"sort"
	"strconv"
	"strings"

	"github.com/dustin/go-humanize"
	"github.com/wagoodman/dive/dive/image"
)

type Evaluation struct {
	Report string
	Pass   bool
}

type Evaluator struct {
	Rules            []Rule
	Results          map[string]RuleResult
	Tally            ResultTally
	Pass             bool
	Misconfigured    bool
	InefficientFiles []ReferenceFile
	format           format
}

type format struct {
	Title       lipgloss.Style
	Success     lipgloss.Style
	Warning     lipgloss.Style
	Disabled    lipgloss.Style
	Failure     lipgloss.Style
	TableHeader lipgloss.Style
	Label       lipgloss.Style
	Aux         lipgloss.Style
	Value       lipgloss.Style
}

type ResultTally struct {
	Pass  int
	Fail  int
	Skip  int
	Warn  int
	Total int
}

type ReferenceFile struct {
	References int    `json:"count"`
	SizeBytes  uint64 `json:"sizeBytes"`
	Path       string `json:"file"`
}

func NewEvaluator(rules []Rule) Evaluator {
	return Evaluator{
		Rules:   rules,
		Results: make(map[string]RuleResult),
		Pass:    true,
		format: format{
			Title:       lipgloss.NewStyle().Bold(true),
			Success:     lipgloss.NewStyle().Foreground(lipgloss.Color("2")),
			Warning:     lipgloss.NewStyle().Foreground(lipgloss.Color("3")),
			Disabled:    lipgloss.NewStyle().Faint(true),
			Failure:     lipgloss.NewStyle().Foreground(lipgloss.Color("1")).Bold(true),
			TableHeader: lipgloss.NewStyle().Bold(true),
			Label:       lipgloss.NewStyle().Width(18),
			Aux:         lipgloss.NewStyle().Faint(true),
			Value:       lipgloss.NewStyle(),
		},
	}
}

func (e Evaluator) isRuleEnabled(rule Rule) bool {
	return rule.Configuration() != "disabled"
}

func (e Evaluator) Evaluate(ctx context.Context, analysis *image.Analysis) Evaluation {
	for _, rule := range e.Rules {
		if !e.isRuleEnabled(rule) {
			e.Results[rule.Key()] = RuleResult{
				status:  RuleConfigured,
				message: "rule disabled",
			}
			continue
		}

		e.Results[rule.Key()] = RuleResult{
			status:  RuleConfigured,
			message: "test",
		}
	}

	// capture inefficient files
	for idx := 0; idx < len(analysis.Inefficiencies); idx++ {
		fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx]

		e.InefficientFiles = append(e.InefficientFiles, ReferenceFile{
			References: len(fileData.Nodes),
			SizeBytes:  uint64(fileData.CumulativeSize),
			Path:       fileData.Path,
		})
	}

	// evaluate results against the configured CI rules
	for _, rule := range e.Rules {
		if !e.isRuleEnabled(rule) {
			e.Results[rule.Key()] = RuleResult{
				status:  RuleDisabled,
				message: "disabled",
			}
			continue
		}

		status, message := rule.Evaluate(analysis)

		if value, exists := e.Results[rule.Key()]; exists && value.status != RuleConfigured && value.status != RuleMisconfigured {
			panic(fmt.Errorf("CI rule result recorded twice: %s", rule.Key()))
		}

		if status == RuleFailed {
			e.Pass = false
		}

		if message == "" {
			message = rule.Configuration()
		}

		e.Results[rule.Key()] = RuleResult{
			status:  status,
			message: message,
		}
	}

	e.Tally.Total = len(e.Results)
	for rule, result := range e.Results {
		switch result.status {
		case RulePassed:
			e.Tally.Pass++
		case RuleFailed:
			e.Tally.Fail++
		case RuleWarning:
			e.Tally.Warn++
		case RuleDisabled:
			e.Tally.Skip++
		default:
			panic(fmt.Errorf("unknown test status (rule='%v'): %v", rule, result.status))
		}
	}

	return Evaluation{
		Report: e.report(analysis),
		Pass:   e.Pass,
	}
}

func (e Evaluator) report(analysis *image.Analysis) string {
	sections := []string{
		e.renderAnalysisSection(analysis),
		e.renderInefficientFilesSection(analysis),
		e.renderEvaluationSection(),
	}

	return strings.Join(sections, "\n\n")
}

func (e Evaluator) renderAnalysisSection(analysis *image.Analysis) string {
	wastedByteStr := ""
	userWastedPercent := "0 %"

	if analysis.WastedBytes > 0 {
		wastedByteStr = fmt.Sprintf("(%s)", humanize.Bytes(analysis.WastedBytes))
		userWastedPercent = fmt.Sprintf("%.2f %%", analysis.WastedUserPercent*100)
	}

	title := e.format.Title.Render("Analysis:")

	rows := []string{
		formatKeyValue(e.format, "efficiency", fmt.Sprintf("%.2f %%", analysis.Efficiency*100)),
		formatKeyValue(e.format, "wastedBytes", fmt.Sprintf("%d bytes %s", analysis.WastedBytes, wastedByteStr)),
		formatKeyValue(e.format, "userWastedPercent", userWastedPercent),
	}

	return title + "\n" + strings.Join(rows, "\n")
}

func (e Evaluator) renderInefficientFilesSection(analysis *image.Analysis) string {
	title := e.format.Title.Render("Inefficient Files:")

	if len(analysis.Inefficiencies) == 0 {
		return title + " (None)"
	}

	header := e.format.TableHeader.Render(
		fmt.Sprintf("  %-5s  %-12s  %-s", "Count", "Wasted Space", "File Path"),
	)

	rows := []string{header}
	for _, file := range e.InefficientFiles {
		row := fmt.Sprintf("  %-5s  %-12s  %-s",
			strconv.Itoa(file.References),
			humanize.Bytes(file.SizeBytes),
			file.Path,
		)
		rows = append(rows, row)
	}

	return title + "\n" + strings.Join(rows, "\n")
}

func (e Evaluator) renderEvaluationSection() string {
	title := e.format.Title.Render("Evaluation:")

	// sort rules by name for consistent output
	rules := make([]string, 0, len(e.Results))
	for name := range e.Results {
		rules = append(rules, name)
	}
	sort.Strings(rules)

	ruleResults := []string{}
	for _, rule := range rules {
		result := e.Results[rule]
		ruleResult := e.formatRuleResult(rule, result)
		ruleResults = append(ruleResults, ruleResult)
	}

	status := e.renderStatusSummary()

	return title + "\n" + strings.Join(ruleResults, "\n") + "\n\n" + status
}

func (e Evaluator) formatRuleResult(ruleName string, result RuleResult) string {
	var style lipgloss.Style
	textStyle := lipgloss.NewStyle()
	switch result.status {
	case RulePassed:
		style = e.format.Success
	case RuleFailed:
		style = e.format.Failure
	case RuleWarning, RuleMisconfigured:
		style = e.format.Warning
	case RuleDisabled:
		style = e.format.Disabled
		textStyle = e.format.Disabled
	default:
		style = lipgloss.NewStyle()
	}

	statusStr := style.Render(result.status.String(e.format))

	if result.message != "" {
		return fmt.Sprintf("  %s  %s", statusStr, textStyle.Render(ruleName+" ("+result.message+")"))
	}

	return fmt.Sprintf("  %s  %s", statusStr, textStyle.Render(ruleName))
}

func (e Evaluator) renderStatusSummary() string {
	if e.Misconfigured {
		return e.format.Failure.Render("CI Misconfigured")
	}

	status := "PASS"
	if e.Tally.Fail > 0 {
		status = "FAIL"
	}

	parts := []string{}

	type tallyItem struct {
		name  string
		value int
	}

	items := []tallyItem{
		//{"total", e.Tally.Total},
		{"pass", e.Tally.Pass},
		{"fail", e.Tally.Fail},
		{"warn", e.Tally.Warn},
		{"skip", e.Tally.Skip},
	}

	for _, item := range items {
		if item.value > 0 {
			parts = append(parts, fmt.Sprintf("%s:%d", item.name, item.value))
		}
	}

	auxSummary := e.format.Aux.Render(" [" + strings.Join(parts, " ") + "]")

	var style lipgloss.Style
	switch {
	case e.Pass && e.Tally.Warn == 0:
		style = e.format.Success
	case e.Pass && e.Tally.Warn > 0:
		style = e.format.Warning
	default:
		style = e.format.Failure
	}
	return style.Render(status) + auxSummary
}

func formatKeyValue(f format, key, value string) string {
	formattedKey := f.Label.Render(key + ":")
	return fmt.Sprintf("  %s %s", formattedKey, value)
}


================================================
FILE: cmd/dive/cli/internal/command/ci/evaluator_test.go
================================================
package ci

import (
	"context"
	"github.com/stretchr/testify/require"
	"go.uber.org/atomic"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/wagoodman/dive/dive/image/docker"
)

var repoRootCache atomic.String

func Test_Evaluator(t *testing.T) {
	result := docker.TestAnalysisFromArchive(t, repoPath(t, ".data/test-docker-image.tar"))

	validTests := []struct {
		name           string
		efficiency     string
		wastedBytes    string
		wastedPercent  string
		expectedPass   bool
		expectedResult map[string]RuleStatus
	}{
		{
			name:          "allFail",
			efficiency:    "0.99",
			wastedBytes:   "1B",
			wastedPercent: "0.01",
			expectedPass:  false,
			expectedResult: map[string]RuleStatus{
				"lowestEfficiency":         RuleFailed,
				"highestWastedBytes":       RuleFailed,
				"highestUserWastedPercent": RuleFailed,
			},
		},
		{
			name:          "allPass",
			efficiency:    "0.9",
			wastedBytes:   "50kB",
			wastedPercent: "0.5",
			expectedPass:  true,
			expectedResult: map[string]RuleStatus{
				"lowestEfficiency":         RulePassed,
				"highestWastedBytes":       RulePassed,
				"highestUserWastedPercent": RulePassed,
			},
		},
		{
			name:          "allDisabled",
			efficiency:    "disabled",
			wastedBytes:   "disabled",
			wastedPercent: "disabled",
			expectedPass:  true,
			expectedResult: map[string]RuleStatus{
				"lowestEfficiency":         RuleDisabled,
				"highestWastedBytes":       RuleDisabled,
				"highestUserWastedPercent": RuleDisabled,
			},
		},
		{
			name:          "mixedResults",
			efficiency:    "0.9",
			wastedBytes:   "1B",
			wastedPercent: "0.5",
			expectedPass:  false,
			expectedResult: map[string]RuleStatus{
				"lowestEfficiency":         RulePassed,
				"highestWastedBytes":       RuleFailed,
				"highestUserWastedPercent": RulePassed,
			},
		},
	}

	for _, test := range validTests {
		t.Run(test.name, func(t *testing.T) {
			// Create rules - these should not error
			rules, err := Rules(test.efficiency, test.wastedBytes, test.wastedPercent)
			require.NoError(t, err)

			evaluator := NewEvaluator(rules)
			eval := evaluator.Evaluate(context.TODO(), result)

			if test.expectedPass != eval.Pass {
				t.Errorf("expected pass=%v, got %v", test.expectedPass, eval.Pass)
			}

			if len(test.expectedResult) != len(evaluator.Results) {
				t.Errorf("expected %v results, got %v", len(test.expectedResult), len(evaluator.Results))
			}

			for rule, actualResult := range evaluator.Results {
				expectedStatus := test.expectedResult[rule]
				if expectedStatus != actualResult.status {
					t.Errorf("%v: expected %v rule status, got %v: %v",
						rule, expectedStatus, actualResult.status, actualResult)
				}
			}
		})
	}

}

func Test_Evaluator_Misconfigurations(t *testing.T) {
	invalidTests := []struct {
		name          string
		efficiency    string
		wastedBytes   string
		wastedPercent string
		expectError   bool
	}{
		{
			name:          "invalid_efficiency_too_high",
			efficiency:    "1.1", // fail!
			wastedBytes:   "50kB",
			wastedPercent: "0.5",
			expectError:   true,
		},
		{
			name:          "invalid_efficiency_too_low",
			efficiency:    "-0.1", // fail!
			wastedBytes:   "50kB",
			wastedPercent: "0.5",
			expectError:   true,
		},
		{
			name:          "invalid_efficiency_format",
			efficiency:    "not_a_number", // fail!
			wastedBytes:   "50kB",
			wastedPercent: "0.5",
			expectError:   true,
		},
		{
			name:          "invalid_wasted_bytes_format",
			efficiency:    "0.9",
			wastedBytes:   "not_a_size", // fail!
			wastedPercent: "0.5",
			expectError:   true,
		},
		{
			name:          "invalid_wasted_percent_high",
			efficiency:    "0.9",
			wastedBytes:   "50kB",
			wastedPercent: "1.1", // fail!
			expectError:   true,
		},
		{
			name:          "invalid_wasted_percent_low",
			efficiency:    "0.9",
			wastedBytes:   "50kB",
			wastedPercent: "-0.1", // fail!
			expectError:   true,
		},
		{
			name:          "invalid_wasted_percent_format",
			efficiency:    "0.9",
			wastedBytes:   "50kB",
			wastedPercent: "not_a_number", // fail!
			expectError:   true,
		},
	}

	for _, test := range invalidTests {
		t.Run(test.name, func(t *testing.T) {
			_, err := Rules(test.efficiency, test.wastedBytes, test.wastedPercent)
			if test.expectError {
				require.Error(t, err, "Expected an error for invalid configuration")
			} else {
				require.NoError(t, err, "Expected no error for valid configuration")
			}
		})
	}
}

func repoPath(t testing.TB, path string) string {
	t.Helper()
	root := repoRoot(t)
	return filepath.Join(root, path)
}

func repoRoot(t testing.TB) string {
	val := repoRootCache.Load()
	if val != "" {
		return val
	}
	t.Helper()
	// use git to find the root of the repo
	out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
	if err != nil {
		t.Fatalf("failed to get repo root: %v", err)
	}
	val = strings.TrimSpace(string(out))
	repoRootCache.Store(val)
	return val
}


================================================
FILE: cmd/dive/cli/internal/command/ci/rule.go
================================================
package ci

import (
	"github.com/wagoodman/dive/dive/image"
)

const (
	RuleUnknown = iota
	RulePassed
	RuleFailed
	RuleWarning
	RuleDisabled
	RuleMisconfigured
	RuleConfigured
)

type Rule interface {
	Key() string
	Configuration() string
	Evaluate(result *image.Analysis) (RuleStatus, string)
}

type RuleStatus int

type RuleResult struct {
	status  RuleStatus
	message string
}

func (status RuleStatus) String(f format) string {
	switch status {
	case RulePassed:
		return f.Success.Render("PASS")
	case RuleFailed:
		return f.Failure.Render("FAIL")
	case RuleWarning:
		return f.Warning.Render("WARN")
	case RuleDisabled:
		return f.Disabled.Render("SKIP")
	case RuleMisconfigured:
		return f.Warning.Render("MISCONFIGURED")
	case RuleConfigured:
		return "CONFIGURED   "
	default:
		return f.Warning.Render("Unknown")
	}
}


================================================
FILE: cmd/dive/cli/internal/command/ci/rules.go
================================================
package ci

import (
	"errors"
	"fmt"
	"github.com/dustin/go-humanize"
	"github.com/wagoodman/dive/dive/image"
	"strconv"
	"strings"
)

const (
	ciKeyLowestEfficiencyThreshold = "lowestEfficiency"
	ciKeyHighestWastedBytes        = "highestWastedBytes"
	ciKeyHighestUserWastedPercent  = "highestUserWastedPercent"
)

func Rules(lowerEfficiency, highestWastedBytes, highestUserWastedPercent string) ([]Rule, error) {
	var rules []Rule
	var errs []error

	lowestEfficiencyRule, err := NewLowestEfficiencyRule(lowerEfficiency)
	if err != nil {
		errs = append(errs, err)
	}
	rules = append(rules, lowestEfficiencyRule)

	highestWastedBytesRule, err := NewHighestWastedBytesRule(highestWastedBytes)
	if err != nil {
		errs = append(errs, err)
	}
	rules = append(rules, highestWastedBytesRule)

	highestUserWastedPercentRule, err := NewHighestUserWastedPercentRule(highestUserWastedPercent)
	if err != nil {
		errs = append(errs, err)
	}
	rules = append(rules, highestUserWastedPercentRule)

	return rules, errors.Join(errs...)

}

func DisabledRule(key string) Rule {
	return &BaseRule{
		key:         key,
		configValue: "disabled",
		evaluator: func(_ *image.Analysis) (RuleStatus, string) {
			return RuleDisabled, "rule disabled"
		},
	}
}

type BaseRule struct {
	key         string
	configValue string
	status      RuleStatus
	evaluator   func(*image.Analysis) (RuleStatus, string)
}

func (rule *BaseRule) Key() string {
	return rule.key
}

func (rule *BaseRule) Configuration() string {
	return rule.configValue
}

func (rule *BaseRule) Evaluate(result *image.Analysis) (RuleStatus, string) {
	if rule.status != RuleUnknown {
		return rule.status, ""
	}
	return rule.evaluator(result)
}

// LowestEfficiencyRule checks if image efficiency is above threshold
type LowestEfficiencyRule struct {
	BaseRule
	threshold float64
}

// HighestWastedBytesRule checks if wasted bytes are below threshold
type HighestWastedBytesRule struct {
	BaseRule
	threshold uint64
}

// HighestUserWastedPercentRule checks if percentage of wasted bytes is below threshold
type HighestUserWastedPercentRule struct {
	BaseRule
	threshold float64
}

func NewLowestEfficiencyRule(configValue string) (Rule, error) {
	if isRuleDisabled(configValue) {
		return DisabledRule(ciKeyLowestEfficiencyThreshold), nil
	}

	threshold, err := strconv.ParseFloat(configValue, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid %s config value, given %q: %v",
			ciKeyLowestEfficiencyThreshold, configValue, err)
	}

	if threshold < 0 || threshold > 1 {
		return nil, fmt.Errorf("%s config value is outside allowed range (0-1), given '%f'",
			ciKeyLowestEfficiencyThreshold, threshold)
	}

	return &LowestEfficiencyRule{
		BaseRule: BaseRule{
			key:         ciKeyLowestEfficiencyThreshold,
			configValue: configValue,
		},
		threshold: threshold,
	}, nil
}

func (r *LowestEfficiencyRule) Evaluate(analysis *image.Analysis) (RuleStatus, string) {
	if r.threshold > analysis.Efficiency {
		return RuleFailed, fmt.Sprintf(
			"image efficiency is too low (efficiency=%2.2f < threshold=%v)",
			analysis.Efficiency, r.threshold)
	}
	return RulePassed, ""
}

// NewHighestWastedBytesRule creates a new rule to check wasted bytes
func NewHighestWastedBytesRule(configValue string) (Rule, error) {
	if isRuleDisabled(configValue) {
		return DisabledRule(ciKeyHighestWastedBytes), nil
	}

	threshold, err := humanize.ParseBytes(configValue)
	if err != nil {
		return nil, fmt.Errorf("invalid highestWastedBytes config value, given %q: %v",
			configValue, err)
	}

	return &HighestWastedBytesRule{
		BaseRule: BaseRule{
			key:         ciKeyHighestWastedBytes,
			configValue: configValue,
		},
		threshold: threshold,
	}, nil
}

func (r *HighestWastedBytesRule) Evaluate(analysis *image.Analysis) (RuleStatus, string) {
	if analysis.WastedBytes > r.threshold {
		return RuleFailed, fmt.Sprintf(
			"too many bytes wasted (wasted-bytes=%d > threshold=%v)",
			analysis.WastedBytes, r.threshold)
	}
	return RulePassed, ""
}

// NewHighestUserWastedPercentRule creates a new rule to check percentage of wasted bytes
func NewHighestUserWastedPercentRule(configValue string) (Rule, error) {
	if isRuleDisabled(configValue) {
		return DisabledRule(ciKeyHighestUserWastedPercent), nil
	}

	threshold, err := strconv.ParseFloat(configValue, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid highestUserWastedPercent config value, given %q: %v",
			configValue, err)
	}

	if threshold < 0 || threshold > 1 {
		return nil, fmt.Errorf("highestUserWastedPercent config value is outside allowed range (0-1), given '%f'",
			threshold)
	}

	return &HighestUserWastedPercentRule{
		BaseRule: BaseRule{
			key:         ciKeyHighestUserWastedPercent,
			configValue: configValue,
		},
		threshold: threshold,
	}, nil
}

func (r *HighestUserWastedPercentRule) Evaluate(analysis *image.Analysis) (RuleStatus, string) {
	if analysis.WastedUserPercent > r.threshold {
		return RuleFailed, fmt.Sprintf(
			"too many bytes wasted, relative to the user bytes added (%%-user-wasted-bytes=%2.2f > threshold=%v)",
			analysis.WastedUserPercent, r.threshold)
	}
	return RulePassed, ""
}

func isRuleDisabled(value string) bool {
	value = strings.TrimSpace(strings.ToLower(value))
	return value == "" || value == "disabled" || value == "off" || value == "false"
}


================================================
FILE: cmd/dive/cli/internal/command/export/export.go
================================================
package export

import (
	"encoding/json"
	"github.com/wagoodman/dive/dive/filetree"
	diveImage "github.com/wagoodman/dive/dive/image"
	"github.com/wagoodman/dive/internal/log"
)

type Export struct {
	Layer []Layer `json:"layer"`
	Image Image   `json:"image"`
}

type Layer struct {
	Index     int                 `json:"index"`
	ID        string              `json:"id"`
	DigestID  string              `json:"digestId"`
	SizeBytes uint64              `json:"sizeBytes"`
	Command   string              `json:"command"`
	FileList  []filetree.FileInfo `json:"fileList"`
}

type Image struct {
	SizeBytes        uint64          `json:"sizeBytes"`
	InefficientBytes uint64          `json:"inefficientBytes"`
	EfficiencyScore  float64         `json:"efficiencyScore"`
	InefficientFiles []FileReference `json:"fileReference"`
}

type FileReference struct {
	References int    `json:"count"`
	SizeBytes  uint64 `json:"sizeBytes"`
	Path       string `json:"file"`
}

// NewExport exports the analysis to a JSON
func NewExport(analysis *diveImage.Analysis) *Export {
	data := Export{
		Layer: make([]Layer, len(analysis.Layers)),
		Image: Image{
			InefficientFiles: make([]FileReference, len(analysis.Inefficiencies)),
			SizeBytes:        analysis.SizeBytes,
			EfficiencyScore:  analysis.Efficiency,
			InefficientBytes: analysis.WastedBytes,
		},
	}

	// export layers in order
	for idx, curLayer := range analysis.Layers {
		layerFileList := make([]filetree.FileInfo, 0)
		visitor := func(node *filetree.FileNode) error {
			layerFileList = append(layerFileList, node.Data.FileInfo)
			return nil
		}
		err := curLayer.Tree.VisitDepthChildFirst(visitor, nil)
		if err != nil {
			log.WithFields("layer", curLayer.Id, "error", err).Debug("unable to propagate layer tree")
		}
		data.Layer[idx] = Layer{
			Index:     curLayer.Index,
			ID:        curLayer.Id,
			DigestID:  curLayer.Digest,
			SizeBytes: curLayer.Size,
			Command:   curLayer.Command,
			FileList:  layerFileList,
		}
	}

	// add file references
	for idx := 0; idx < len(analysis.Inefficiencies); idx++ {
		fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx]

		data.Image.InefficientFiles[idx] = FileReference{
			References: len(fileData.Nodes),
			SizeBytes:  uint64(fileData.CumulativeSize),
			Path:       fileData.Path,
		}
	}

	return &data
}

func (exp *Export) Marshal() ([]byte, error) {
	return json.MarshalIndent(&exp, "", "  ")
}


================================================
FILE: cmd/dive/cli/internal/command/export/export_test.go
================================================
package export

import (
	"testing"

	"github.com/wagoodman/dive/dive/image/docker"
)

func Test_Export(t *testing.T) {
	result := docker.TestAnalysisFromArchive(t, repoPath(t, ".data/test-docker-image.tar"))

	export := NewExport(result)
	payload, err := export.Marshal()
	if err != nil {
		t.Errorf("Test_Export: unable to export analysis: %v", err)
	}

	snaps.MatchJSON(t, payload)
}


================================================
FILE: cmd/dive/cli/internal/command/export/main_test.go
================================================
package export

import (
	"flag"
	"github.com/charmbracelet/lipgloss"
	snapsPkg "github.com/gkampitakis/go-snaps/snaps"
	"github.com/muesli/termenv"
	"github.com/stretchr/testify/require"
	"go.uber.org/atomic"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

var (
	updateSnapshot = flag.Bool("update", false, "update any test snapshots")
	snaps          *snapsPkg.Config
	repoRootCache  atomic.String
)

func TestMain(m *testing.M) {
	// flags are not parsed until after test.Main is called...
	flag.Parse()

	os.Unsetenv("DIVE_CONFIG")

	// disable colors
	lipgloss.SetColorProfile(termenv.Ascii)

	snaps = snapsPkg.WithConfig(
		snapsPkg.Update(*updateSnapshot),
		snapsPkg.Dir("testdata/snapshots"),
	)

	v := m.Run()

	snapsPkg.Clean(m)

	os.Exit(v)
}

func TestUpdateSnapshotDisabled(t *testing.T) {
	require.False(t, *updateSnapshot, "update snapshot flag should be disabled")
}

func repoPath(t testing.TB, path string) string {
	t.Helper()
	root := repoRoot(t)
	return filepath.Join(root, path)
}

func repoRoot(t testing.TB) string {
	val := repoRootCache.Load()
	if val != "" {
		return val
	}
	t.Helper()
	// use git to find the root of the repo
	out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
	if err != nil {
		t.Fatalf("failed to get repo root: %v", err)
	}
	val = strings.TrimSpace(string(out))
	repoRootCache.Store(val)
	return val
}


================================================
FILE: cmd/dive/cli/internal/command/export/testdata/snapshots/export_test.snap
================================================

[Test_Export - 1]
{
 "image": {
  "efficiencyScore": 0.9844212134184309,
  "fileReference": [
   {
    "count": 2,
    "file": "/root/saved.txt",
    "sizeBytes": 12810
   },
   {
    "count": 2,
    "file": "/root/example/somefile1.txt",
    "sizeBytes": 12810
   },
   {
    "count": 2,
    "file": "/root/example/somefile3.txt",
    "sizeBytes": 6405
   }
  ],
  "inefficientBytes": 32025,
  "sizeBytes": 1220598
 },
 "layer": [
  {
   "command": "#(nop) ADD file:ce026b62356eec3ad1214f92be2c9dc063fe205bd5e600be3492c4dfb17148bd in / ",
   "digestId": "sha256:23bc2b70b2014dec0ac22f27bb93e9babd08cdd6f1115d0c955b9ff22b382f5a",
   "fileList": [
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "bin/[",
     "size": 1075464,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/[[",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/acpid",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/add-shell",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/addgroup",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/adduser",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/adjtimex",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ar",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/arch",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/arp",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/arping",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ash",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/awk",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/base64",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/basename",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/beep",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/blkdiscard",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/blkid",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/blockdev",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/bootchartd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/brctl",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/bunzip2",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/busybox",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/bzcat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/bzip2",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cal",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chattr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chgrp",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chmod",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chown",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chpasswd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chpst",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chroot",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chrt",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/chvt",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cksum",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/clear",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cmp",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/comm",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/conspy",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cp",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cpio",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/crond",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/crontab",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cryptpw",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cttyhack",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/cut",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/date",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/deallocvt",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/delgroup",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/deluser",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/depmod",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/devmem",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/df",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dhcprelay",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/diff",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dirname",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dmesg",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dnsd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dnsdomainname",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dos2unix",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dpkg",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dpkg-deb",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/du",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dumpkmap",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/dumpleases",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/echo",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ed",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/egrep",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/eject",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/env",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/envdir",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/envuidgid",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ether-wake",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/expand",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/expr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/factor",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fakeidentd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fallocate",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/false",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fatattr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fbset",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fbsplash",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fdflush",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fdformat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fdisk",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fgconsole",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fgrep",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/find",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/findfs",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/flock",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fold",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/free",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/freeramdisk",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fsck",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fsck.minix",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fsfreeze",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fstrim",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fsync",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ftpd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ftpget",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ftpput",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/fuser",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "bin/getconf",
     "size": 77880,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/getopt",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/getty",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/grep",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/groups",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/gunzip",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/gzip",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/halt",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hdparm",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/head",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hexdump",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hexedit",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hostid",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hostname",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/httpd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hush",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/hwclock",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/i2cdetect",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/i2cdump",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/i2cget",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/i2cset",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/id",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ifconfig",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ifdown",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ifenslave",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ifplugd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ifup",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/inetd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/init",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/insmod",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/install",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ionice",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/iostat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ip",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ipaddr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ipcalc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ipcrm",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ipcs",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/iplink",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ipneigh",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/iproute",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/iprule",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/iptunnel",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/kbd_mode",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/kill",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/killall",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/killall5",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/klogd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/last",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/less",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/link",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/linux32",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/linux64",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/linuxrc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ln",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/loadfont",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/loadkmap",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/logger",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/login",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/logname",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/logread",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/losetup",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lpd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lpq",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lpr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ls",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lsattr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lsmod",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lsof",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lspci",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lsscsi",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lsusb",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lzcat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lzma",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/lzop",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/makedevs",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/makemime",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/man",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/md5sum",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mdev",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mesg",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/microcom",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkdir",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkdosfs",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mke2fs",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkfifo",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkfs.ext2",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkfs.minix",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkfs.vfat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mknod",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkpasswd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mkswap",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mktemp",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/modinfo",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/modprobe",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/more",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mount",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mountpoint",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mpstat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mt",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/mv",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nameif",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nanddump",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nandwrite",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nbd-client",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/netstat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nice",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nl",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nmeter",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nohup",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nproc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nsenter",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nslookup",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ntpd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/nuke",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/od",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/openvt",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/partprobe",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/passwd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/paste",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/patch",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pgrep",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pidof",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ping",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ping6",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pipe_progress",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pivot_root",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pkill",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pmap",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/popmaildir",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/poweroff",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/powertop",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/printenv",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/printf",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ps",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pscan",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pstree",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pwd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/pwdx",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/raidautorun",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rdate",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rdev",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/readahead",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/readlink",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/readprofile",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/realpath",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/reboot",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/reformime",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/remove-shell",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/renice",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/reset",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/resize",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/resume",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rev",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rm",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rmdir",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rmmod",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/route",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rpm",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rpm2cpio",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rtcwake",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/run-init",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/run-parts",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/runlevel",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/runsv",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/runsvdir",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/rx",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/script",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/scriptreplay",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sed",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sendmail",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/seq",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setarch",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setconsole",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setfattr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setfont",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setkeycodes",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setlogcons",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setpriv",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setserial",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setsid",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/setuidgid",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sh",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sha1sum",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sha256sum",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sha3sum",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sha512sum",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/showkey",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/shred",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/shuf",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/slattach",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sleep",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/smemcap",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/softlimit",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sort",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/split",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ssl_client",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/start-stop-daemon",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/stat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/strings",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/stty",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/su",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sulogin",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sum",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sv",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/svc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/svlogd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/svok",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/swapoff",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/swapon",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/switch_root",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sync",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/sysctl",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/syslogd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tac",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tail",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tar",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/taskset",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tcpsvd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tee",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/telnet",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/telnetd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/test",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tftp",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tftpd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/time",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/timeout",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/top",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/touch",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tr",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/traceroute",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/traceroute6",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/true",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/truncate",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tty",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ttysize",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/tunctl",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ubiattach",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ubidetach",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ubimkvol",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ubirename",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ubirmvol",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ubirsvol",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/ubiupdatevol",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/udhcpc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/udhcpd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/udpsvd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/uevent",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/umount",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/uname",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/unexpand",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/uniq",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/unix2dos",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/unlink",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/unlzma",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/unshare",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/unxz",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/unzip",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/uptime",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/users",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/usleep",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/uudecode",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/uuencode",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/vconfig",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/vi",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/vlock",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/volname",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/w",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/wall",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/watch",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/watchdog",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/wc",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/wget",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/which",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/who",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/whoami",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/whois",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/xargs",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/xxd",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/xz",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/xzcat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/yes",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/zcat",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "bin/[",
     "path": "bin/zcip",
     "size": 0,
     "typeFlag": 49,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "bin",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "dev",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 436,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "etc/group",
     "size": 307,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "etc/localtime",
     "size": 127,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "etc/network/if-down.d",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "etc/network/if-post-down.d",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "etc/network/if-pre-up.d",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "etc/network/if-up.d",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "etc/network",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "etc/passwd",
     "size": 340,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 384,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "etc/shadow",
     "size": 243,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "etc",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 65534,
     "isDir": true,
     "linkName": "",
     "path": "home",
     "size": 0,
     "typeFlag": 53,
     "uid": 65534
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2148532735,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "tmp",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 1,
     "isDir": true,
     "linkName": "",
     "path": "usr/sbin",
     "size": 0,
     "typeFlag": 53,
     "uid": 1
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "usr",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 8,
     "isDir": true,
     "linkName": "",
     "path": "var/spool/mail",
     "size": 0,
     "typeFlag": 53,
     "uid": 8
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "var/spool",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "var/www",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "var",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "28cfe03618aa2e914e81fdd90345245c15f4478e35252c06ca52d238fd3cc694",
   "index": 0,
   "sizeBytes": 1154361
  },
  {
   "command": "#(nop) ADD file:139c3708fb6261126453e34483abd8bf7b26ed16d952fd976994d68e72d93be2 in /somefile.txt ",
   "digestId": "sha256:a65b7d7ac139a0e4337bc3c73ce511f937d6140ef61a0108f7d4b8aab8d67274",
   "fileList": [
    {
     "fileMode": 436,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "somefile.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    }
   ],
   "id": "1871059774abe6914075e4a919b778fa1561f577d620ae52438a9635e6241936",
   "index": 1,
   "sizeBytes": 6405
  },
  {
   "command": "mkdir -p /root/example/really/nested",
   "digestId": "sha256:93e208d471756ffbac88cf9c25feb442007f221d3bd73231e27b747a0a68927c",
   "fileList": [
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example/really/nested",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example/really",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "49fe2a475548bfa4d493fc796fce41f30704e3d4cbff3e45dd3e06f463236d1d",
   "index": 2,
   "sizeBytes": 0
  },
  {
   "command": "cp /somefile.txt /root/example/somefile1.txt",
   "digestId": "sha256:4abad3abe3cb99ad7a492a9d9f6b3d66287c1646843c74128bbbec4f7be5aa9e",
   "fileList": [
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/example/somefile1.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "80cd2ca1ffc89962b9349c80280c2bc551acbd11e09b16badb0669f8e2369020",
   "index": 3,
   "sizeBytes": 6405
  },
  {
   "command": "chmod 444 /root/example/somefile1.txt",
   "digestId": "sha256:14c9a6ffcb6a0f32d1035f97373b19608e2d307961d8be156321c3f1c1504cbf",
   "fileList": [
    {
     "fileMode": 292,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/example/somefile1.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "c99e2f8d3f6282668f0d30dc1db5e67a51d7a1dcd7ff6ddfa0f90760836778ec",
   "index": 4,
   "sizeBytes": 6405
  },
  {
   "command": "cp /somefile.txt /root/example/somefile2.txt",
   "digestId": "sha256:778fb5770ef466f314e79cc9dc418eba76bfc0a64491ce7b167b76aa52c736c4",
   "fileList": [
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/example/somefile2.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "5eca617bdc3bc06134fe957a30da4c57adb7c340a6d749c8edc4c15861c928d7",
   "index": 5,
   "sizeBytes": 6405
  },
  {
   "command": "cp /somefile.txt /root/example/somefile3.txt",
   "digestId": "sha256:f275b8a31a71deb521cc048e6021e2ff6fa52bedb25c9b7bbe129a0195ddca5f",
   "fileList": [
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/example/somefile3.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "f07c3eb887572395408f8e11a07af945e4da5f02b3188bb06b93fad713ca0b99",
   "index": 6,
   "sizeBytes": 6405
  },
  {
   "command": "mv /root/example/somefile3.txt /root/saved.txt",
   "digestId": "sha256:dd1effc5eb19894c3e9b57411c98dd1cf30fa1de4253c7fae53c9cea67267d83",
   "fileList": [
    {
     "fileMode": 0,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/example/.wh.somefile3.txt",
     "size": 0,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/example",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/saved.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "461885fc22589158dee3c5b9f01cc41c87805439f58b4399d733b51aa305cbf9",
   "index": 7,
   "sizeBytes": 6405
  },
  {
   "command": "cp /root/saved.txt /root/.saved.txt",
   "digestId": "sha256:8d1869a0a066cdd12e48d648222866e77b5e2814f773bb3bd8774ab4052f0f1d",
   "fileList": [
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/.saved.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "a10327f68ffed4afcba78919052809a8f774978a6b87fc117d39c53c4842f72c",
   "index": 8,
   "sizeBytes": 6405
  },
  {
   "command": "rm -rf /root/example/",
   "digestId": "sha256:bc2e36423fa31a97223fd421f22c35466220fa160769abf697b8eb58c896b468",
   "fileList": [
    {
     "fileMode": 0,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/.wh.example",
     "size": 0,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "f2fc54e25cb7966dc9732ec671a77a1c5c104e732bd15ad44a2dc1ac42368f84",
   "index": 9,
   "sizeBytes": 0
  },
  {
   "command": "#(nop) ADD dir:7ec14b81316baa1a31c38c97686a8f030c98cba2035c968412749e33e0c4427e in /root/.data/ ",
   "digestId": "sha256:7f648d45ee7b6de2292162fba498b66cbaaf181da9004fcceef824c72dbae445",
   "fileList": [
    {
     "fileMode": 509,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/.data/tag.sh",
     "size": 917,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/.data/test.sh",
     "size": 1270,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/.data",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "aad36d0b05e71c7e6d4dfe0ca9ed6be89e2e0d8995dafe83438299a314e91071",
   "index": 10,
   "sizeBytes": 2187
  },
  {
   "command": "cp /root/saved.txt /tmp/saved.again1.txt",
   "digestId": "sha256:a4b8f95f266d5c063c9a9473c45f2f85ddc183e37941b5e6b6b9d3c00e8e0457",
   "fileList": [
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "tmp/saved.again1.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2148532735,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "tmp",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "3d4ad907517a021d86a4102d2764ad2161e4818bbd144e41d019bfc955434181",
   "index": 11,
   "sizeBytes": 6405
  },
  {
   "command": "cp /root/saved.txt /root/.data/saved.again2.txt",
   "digestId": "sha256:22a44d45780a541e593a8862d80f3e14cb80b6bf76aa42ce68dc207a35bf3a4a",
   "fileList": [
    {
     "fileMode": 420,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/.data/saved.again2.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484141,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root/.data",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "81b1b002d4b4c1325a9cad9990b5277e7f29f79e0f24582344c0891178f95905",
   "index": 12,
   "sizeBytes": 6405
  },
  {
   "command": "chmod +x /root/saved.txt",
   "digestId": "sha256:ba689cac6a98c92d121fa5c9716a1bab526b8bb1fd6d43625c575b79e97300c5",
   "fileList": [
    {
     "fileMode": 493,
     "gid": 0,
     "isDir": false,
     "linkName": "",
     "path": "root/saved.txt",
     "size": 6405,
     "typeFlag": 48,
     "uid": 0
    },
    {
     "fileMode": 2147484096,
     "gid": 0,
     "isDir": true,
     "linkName": "",
     "path": "root",
     "size": 0,
     "typeFlag": 53,
     "uid": 0
    }
   ],
   "id": "cfb35bb5c127d848739be5ca726057e6e2c77b2849f588e7aebb642c0d3d4b7b",
   "index": 13,
   "sizeBytes": 6405
  }
 ]
}
---


================================================
FILE: cmd/dive/cli/internal/command/root.go
================================================
package command

import (
	"context"
	"errors"
	"fmt"
	"github.com/anchore/clio"
	"github.com/spf13/afero"
	"github.com/spf13/cobra"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/command/adapter"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/options"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/ui"
	"github.com/wagoodman/dive/dive"
	"github.com/wagoodman/dive/dive/image"
	"github.com/wagoodman/dive/internal/bus"
	"os"
)

type rootOptions struct {
	options.Application `yaml:",inline" mapstructure:",squash"`

	// reserved for future use of root-only flags
}

func Root(app clio.Application) *cobra.Command {
	opts := &rootOptions{
		Application: options.DefaultApplication(),
	}
	return app.SetupRootCommand(&cobra.Command{
		Use:   "dive [IMAGE]",
		Short: "Docker Image Visualizer & Explorer",
		Long: `This tool provides a way to discover and explore the contents of a docker image. Additionally the tool estimates
the amount of wasted space and identifies the offending files from the image.`,
		Args: func(cmd *cobra.Command, args []string) error {
			if len(args) != 1 {
				return fmt.Errorf("exactly one argument is required")
			}
			opts.Analysis.Image = args[0]
			return nil
		},
		RunE: func(cmd *cobra.Command, _ []string) error {
			if err := setUI(app, opts.Application); err != nil {
				return fmt.Errorf("failed to set UI: %w", err)
			}

			resolver, err := dive.GetImageResolver(opts.Analysis.Source)
			if err != nil {
				return fmt.Errorf("cannot determine image provider to fetch from: %w", err)
			}

			ctx := cmd.Context()

			img, err := adapter.ImageResolver(resolver).Fetch(ctx, opts.Analysis.Image)
			if err != nil {
				return fmt.Errorf("cannot load image: %w", err)
			}

			return run(ctx, opts.Application, img, resolver)
		},
	}, opts)
}

func setUI(app clio.Application, opts options.Application) error {
	type Stater interface {
		State() *clio.State
	}

	state := app.(Stater).State()

	ux := ui.NewV1UI(opts.V1Preferences(), os.Stdout, state.Config.Log.Quiet, state.Config.Log.Verbosity)
	return state.UI.Replace(ux)
}

func run(ctx context.Context, opts options.Application, img *image.Image, content image.ContentReader) error {
	analysis, err := adapter.NewAnalyzer().Analyze(ctx, img)
	if err != nil {
		return fmt.Errorf("cannot analyze image: %w", err)
	}

	if opts.Export.JsonPath != "" {
		if err := adapter.NewExporter(afero.NewOsFs()).ExportTo(ctx, analysis, opts.Export.JsonPath); err != nil {
			return fmt.Errorf("cannot export analysis: %w", err)
		}
		return nil
	}

	if opts.CI.Enabled {
		eval := adapter.NewEvaluator(opts.CI.Rules.List).Evaluate(ctx, analysis)

		if !eval.Pass {
			return errors.New("evaluation failed")
		}
		return nil
	}

	bus.ExploreAnalysis(*analysis, content)

	return nil
}


================================================
FILE: cmd/dive/cli/internal/options/analysis.go
================================================
package options

import (
	"fmt"
	"github.com/anchore/clio"
	"github.com/scylladb/go-set/strset"
	"github.com/wagoodman/dive/dive"
	"github.com/wagoodman/dive/internal/log"
	"strings"
)

const defaultContainerEngine = "docker"

var _ interface {
	clio.PostLoader
	clio.FieldDescriber
} = (*Analysis)(nil)

// Analysis provides configuration for the image analysis behavior
type Analysis struct {
	Image                     string           `yaml:"image" mapstructure:"-"`
	ContainerEngine           string           `yaml:"container-engine" mapstructure:"container-engine"`
	Source                    dive.ImageSource `yaml:"-" mapstructure:"-"`
	IgnoreErrors              bool             `yaml:"ignore-errors" mapstructure:"ignore-errors"`
	AvailableContainerEngines []string         `yaml:"-" mapstructure:"-"`
}

func DefaultAnalysis() Analysis {
	return Analysis{
		ContainerEngine:           defaultContainerEngine,
		IgnoreErrors:              false,
		AvailableContainerEngines: dive.ImageSources,
	}
}

func (c *Analysis) DescribeFields(descriptions clio.FieldDescriptionSet) {
	descriptions.Add(&c.ContainerEngine, "container engine to use for image analysis (supported options: 'docker' and 'podman')")
	descriptions.Add(&c.IgnoreErrors, "continue with analysis even if there are errors parsing the image archive")
}

func (c *Analysis) AddFlags(flags clio.FlagSet) {
	flags.StringVarP(&c.ContainerEngine, "source", "",
		fmt.Sprintf("The container engine to fetch the image from. Allowed values: %s", strings.Join(c.AvailableContainerEngines, ", ")))

	flags.BoolVarP(&c.IgnoreErrors, "ignore-errors", "i", "ignore image parsing errors and run the analysis anyway")
}

func (c *Analysis) PostLoad() error {
	validEngines := strset.New(c.AvailableContainerEngines...)
	if !validEngines.Has(c.ContainerEngine) {
		log.Warnf("invalid container engine: %s (valid options: %s), using default %q", c.ContainerEngine, strings.Join(c.AvailableContainerEngines, ", "), defaultContainerEngine)
		c.ContainerEngine = "docker"
	}

	if c.Image != "" {
		sourceType, imageStr := dive.DeriveImageSource(c.Image)

		if sourceType == dive.SourceUnknown {
			sourceType = dive.ParseImageSource(c.ContainerEngine)
			if sourceType == dive.SourceUnknown {
				return fmt.Errorf("unable to determine image source from %q: %v\n", c.Image, c.ContainerEngine)
			}

			// use exactly what the user provided
			imageStr = c.Image
		}

		c.Image = imageStr
		c.Source = sourceType
	} else {
		c.Source = dive.ParseImageSource(c.ContainerEngine)
	}

	return nil
}


================================================
FILE: cmd/dive/cli/internal/options/application.go
================================================
package options

import (
	"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1"
)

type Application struct {
	Analysis Analysis `yaml:",inline" mapstructure:",squash"`
	CI       CI       `yaml:",inline" mapstructure:",squash"`
	Export   Export   `yaml:",inline" mapstructure:",squash"`
	UI       UI       `yaml:",inline" mapstructure:",squash"`
}

func DefaultApplication() Application {
	return Application{
		Analysis: DefaultAnalysis(),
		CI:       DefaultCI(),
		Export:   DefaultExport(),
		UI:       DefaultUI(),
	}
}

func (c Application) V1Preferences() v1.Preferences {
	return v1.Preferences{
		KeyBindings:                c.UI.Keybinding.Config,
		ShowFiletreeAttributes:     c.UI.Filetree.ShowAttributes,
		ShowAggregatedLayerChanges: c.UI.Layer.ShowAggregatedChanges,
		CollapseFiletreeDirectory:  c.UI.Filetree.CollapseDir,
		FiletreePaneWidth:          c.UI.Filetree.PaneWidth,
		FiletreeDiffHide:           nil,
	}
}


================================================
FILE: cmd/dive/cli/internal/options/ci.go
================================================
package options

import (
	"fmt"
	"github.com/anchore/clio"
	"gopkg.in/yaml.v3"
	"os"
)

var _ interface {
	clio.PostLoader
	clio.FieldDescriber
	clio.FlagAdder
} = (*CI)(nil)

const defaultCIConfigPath = ".dive-ci"

type CI struct {
	Enabled    bool    `yaml:"ci" mapstructure:"ci"`
	ConfigPath string  `yaml:"ci-config" mapstructure:"ci-config"`
	Rules      CIRules `yaml:"rules" mapstructure:"rules"`
}

func DefaultCI() CI {
	return CI{
		Enabled:    false,
		ConfigPath: defaultCIConfigPath,
		Rules:      DefaultCIRules(),
	}
}

func (c *CI) DescribeFields(descriptions clio.FieldDescriptionSet) {
	descriptions.Add(&c.Enabled, "enable CI mode")
	descriptions.Add(&c.ConfigPath, "path to the CI config file")
}

func (c *CI) AddFlags(flags clio.FlagSet) {
	flags.BoolVarP(&c.Enabled, "ci", "", "skip the interactive TUI and validate against CI rules (same as env var CI=true)")
	flags.StringVarP(&c.ConfigPath, "ci-config", "", "if CI=true in the environment, use the given yaml to drive validation rules.")
}

func (c *CI) PostLoad() error {
	enabledFromEnv := truthy(os.Getenv("CI"))
	if !c.Enabled && enabledFromEnv {
		c.Enabled = true
	}

	if c.ConfigPath != "" {
		if fileExists(c.ConfigPath) {
			// if a config file is provided, load it and override any values provided in the application config.
			// If we're hitting this case we should pretend that only the config file was provided and applied
			// on top of the default config values.
			yamlFile, err := os.ReadFile(c.ConfigPath)
			if err != nil {
				return fmt.Errorf("failed to read CI config file %s: %w", c.ConfigPath, err)
			}
			def := DefaultCIRules()
			r := legacyRuleFile{
				LowestEfficiencyThresholdString: def.LowestEfficiencyThresholdString,
				HighestWastedBytesString:        def.HighestWastedBytesString,
				HighestUserWastedPercentString:  def.HighestUserWastedPercentString,
			}
			wrapper := struct {
				Rules *legacyRuleFile `yaml:"rules"`
			}{
				Rules: &r,
			}
			if err := yaml.Unmarshal(yamlFile, &wrapper); err != nil {
				return fmt.Errorf("failed to unmarshal CI config file %s: %w", c.ConfigPath, err)
			}
			// TODO: should this be a deprecated use warning in the future?
			c.Rules = CIRules{
				LowestEfficiencyThresholdString: r.LowestEfficiencyThresholdString,
				HighestWastedBytesString:        r.HighestWastedBytesString,
				HighestUserWastedPercentString:  r.HighestUserWastedPercentString,
			}
		}
	}

	return nil
}

type legacyRuleFile struct {
	LowestEfficiencyThresholdString string `yaml:"lowestEfficiency"`
	HighestWastedBytesString        string `yaml:"highestWastedBytes"`
	HighestUserWastedPercentString  string `yaml:"highestUserWastedPercent"`
}

func fileExists(path string) bool {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return false
	}
	return true
}

func truthy(value string) bool {
	switch value {
	case "true", "1", "yes":
		return true
	case "false", "0", "no":
		return false
	default:
		return false
	}
}


================================================
FILE: cmd/dive/cli/internal/options/ci_rules.go
================================================
package options

import (
	"github.com/anchore/clio"
	"github.com/wagoodman/dive/cmd/dive/cli/internal/command/ci"
	"github.com/wagoodman/dive/internal/log"
)

type CIRules struct {
	LowestEfficiencyThresholdString       string `yaml:"lowest-efficiency" mapstructure:"lowest-efficiency"`
	LegacyLowestEfficiencyThresholdString string `yaml:"-" mapstructure:"lowestEfficiency"`

	HighestWastedBytesString       string `yaml:"highest-wasted-bytes" mapstructure:"highest-wasted-bytes"`
	LegacyHighestWastedBytesString string `yaml:"-" mapstructure:"highestWastedBytes"`

	HighestUserWastedPercentString       string `yaml:"highest-user-wasted-percent" mapstructure:"highest-user-wasted-percent"`
	LegacyHighestUserWastedPercentString string `yaml:"-" mapstructure:"highestUserWastedPercent"`

	List []ci.Rule `yaml:"-" mapstructure:"-"`
}

func DefaultCIRules() CIRules {
	return CIRules{
		LowestEfficiencyThresholdString: "0.9",
		HighestWastedBytesString:        "disabled",
		HighestUserWastedPercentString:  "0.1",
	}
}

func (c *CIRules) DescribeFields(descriptions clio.FieldDescriptionSet) {
	descriptions.Add(&c.LowestEfficiencyThresholdString, "lowest allowable image efficiency (as a ratio between 0-1), otherwise CI validation will fail.")
	descriptions.Add(&c.HighestWastedBytesString, "highest allowable bytes wasted, otherwise CI validation will fail.")
	descriptions.Add(&c.HighestUserWastedPercentString, "highest allowable percentage of bytes wasted (as a ratio between 0-1), otherwise CI validation will fail.")
}

func (c *CIRules) AddFlags(flags clio.FlagSet) {
	flags.StringVarP(&c.LowestEfficiencyThresholdString, "lowestEfficiency", "", "(only valid with --ci given) lowest allowable image efficiency (as a ratio between 0-1), otherwise CI validation will fail.")
	flags.StringVarP(&c.HighestWastedBytesString, "highestWastedBytes", "", "(only valid with --ci given) highest allowable bytes wasted, otherwise CI validation will fail.")
	flags.StringVarP(&c.HighestUserWastedPercentString, "highestUserWastedPercent", "", "(only valid with --ci given) highest allowable percentage of bytes wasted (as a ratio between 0-1), otherwise CI validation will fail.")
}

func (c CIRules) hasLegacyOptionsInUse() bool {
	return c.LegacyLowestEfficiencyThresholdString != "" || c.LegacyHighestWastedBytesString != "" || c.LegacyHighestUserWastedPercentString != ""
}

func (c *CIRules) PostLoad() error {
	// protect against repeated calls
	c.List = nil

	if c.hasLegacyOptionsInUse() {
		log.Warnf("please specify ci rules in snake-case (the legacy camelCase format is deprecated)")
	}

	if c.LegacyLowestEfficiencyThresholdString != "" {
		c.LowestEfficiencyThresholdString = c.LegacyLowestEfficiencyThresholdString
	}

	if c.LegacyHighestWastedBytesString != "" {
		c.HighestWastedBytesString = c.LegacyHighestWastedBytesString
	}

	if c.LegacyHighestUserWastedPercentString != "" {
		c.HighestUserWastedPercentString = c.LegacyHighestUserWastedPercentString
	}

	rules, err := ci.Rules(c.LowestEfficiencyThresholdString, c.HighestWastedBytesString, c.HighestUserWastedPercentString)
	if err != nil {
		return err
	}
	c.List = append(c.List, rules...)

	return nil
}


================================================
FILE: cmd/dive/cli/internal/options/export.go
================================================
package options

import (
	"fmt"
	"os"
	"path"

	"github.com/anchore/clio"
)

var _ interface {
	clio.FlagAdder
	clio.PostLoader
} = (*Export)(nil)

// Export provides configuration for data export functionality
type Export struct {
	// Path to export analysis results as JSON (empty string = disabled)
	JsonPath string `yaml:"json-path" json:"json-path" mapstructure:"json-path"`
}

func DefaultExport() Export {
	return Export{}
}

func (o *Export) AddFlags(flags clio.FlagSet) {
	flags.StringVarP(&o.JsonPath, "json", "j", "Skip the interactive TUI and write the layer analysis statistics to a given file.")
}

func (o *Export) PostLoad() error {

	if o.JsonPath != "" {
		dir := path.Dir(o.JsonPath)
		if _, err := os.Stat(dir); os.IsNotExist(e
Download .txt
gitextract_iuihkmt4/

├── .binny.yaml
├── .bouncer.yaml
├── .data/
│   ├── .dive-ci
│   ├── Dockerfile.example
│   ├── Dockerfile.minimal
│   └── Dockerfile.test-image
├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── actions/
│   │   └── bootstrap/
│   │       └── action.yaml
│   ├── dependabot.yaml
│   ├── scripts/
│   │   ├── coverage.py
│   │   └── trigger-release.sh
│   └── workflows/
│       ├── release.yaml
│       └── validations.yaml
├── .gitignore
├── .golangci.yaml
├── .goreleaser.yaml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── RELEASE.md
├── Taskfile.yaml
├── cmd/
│   └── dive/
│       ├── cli/
│       │   ├── cli.go
│       │   ├── cli_build_test.go
│       │   ├── cli_ci_test.go
│       │   ├── cli_config_test.go
│       │   ├── cli_json_test.go
│       │   ├── cli_load_test.go
│       │   ├── cli_test.go
│       │   ├── internal/
│       │   │   ├── command/
│       │   │   │   ├── adapter/
│       │   │   │   │   ├── analyzer.go
│       │   │   │   │   ├── evaluator.go
│       │   │   │   │   ├── exporter.go
│       │   │   │   │   └── resolver.go
│       │   │   │   ├── build.go
│       │   │   │   ├── ci/
│       │   │   │   │   ├── evaluator.go
│       │   │   │   │   ├── evaluator_test.go
│       │   │   │   │   ├── rule.go
│       │   │   │   │   └── rules.go
│       │   │   │   ├── export/
│       │   │   │   │   ├── export.go
│       │   │   │   │   ├── export_test.go
│       │   │   │   │   ├── main_test.go
│       │   │   │   │   └── testdata/
│       │   │   │   │       └── snapshots/
│       │   │   │   │           └── export_test.snap
│       │   │   │   └── root.go
│       │   │   ├── options/
│       │   │   │   ├── analysis.go
│       │   │   │   ├── application.go
│       │   │   │   ├── ci.go
│       │   │   │   ├── ci_rules.go
│       │   │   │   ├── export.go
│       │   │   │   ├── ui.go
│       │   │   │   ├── ui_diff.go
│       │   │   │   ├── ui_filetree.go
│       │   │   │   ├── ui_keybindings.go
│       │   │   │   └── ui_layers.go
│       │   │   └── ui/
│       │   │       ├── no_ui.go
│       │   │       ├── v1/
│       │   │       │   ├── app/
│       │   │       │   │   ├── app.go
│       │   │       │   │   ├── controller.go
│       │   │       │   │   ├── job_control_other.go
│       │   │       │   │   └── job_control_unix.go
│       │   │       │   ├── config.go
│       │   │       │   ├── format/
│       │   │       │   │   └── format.go
│       │   │       │   ├── key/
│       │   │       │   │   ├── binding.go
│       │   │       │   │   └── config.go
│       │   │       │   ├── layout/
│       │   │       │   │   ├── area.go
│       │   │       │   │   ├── compound/
│       │   │       │   │   │   └── layer_details_column.go
│       │   │       │   │   ├── layout.go
│       │   │       │   │   ├── location.go
│       │   │       │   │   ├── manager.go
│       │   │       │   │   └── manager_test.go
│       │   │       │   ├── view/
│       │   │       │   │   ├── cursor.go
│       │   │       │   │   ├── debug.go
│       │   │       │   │   ├── filetree.go
│       │   │       │   │   ├── filter.go
│       │   │       │   │   ├── image_details.go
│       │   │       │   │   ├── layer.go
│       │   │       │   │   ├── layer_change_listener.go
│       │   │       │   │   ├── layer_details.go
│       │   │       │   │   ├── renderer.go
│       │   │       │   │   ├── status.go
│       │   │       │   │   └── views.go
│       │   │       │   └── viewmodel/
│       │   │       │       ├── config.go
│       │   │       │       ├── filetree.go
│       │   │       │       ├── filetree_test.go
│       │   │       │       ├── layer_compare.go
│       │   │       │       ├── layer_selection.go
│       │   │       │       ├── layer_set_state.go
│       │   │       │       ├── layer_set_state_test.go
│       │   │       │       └── testdata/
│       │   │       │           ├── TestFileShowAggregateChanges.txt
│       │   │       │           ├── TestFileTreeDirCollapse.txt
│       │   │       │           ├── TestFileTreeDirCollapseAll.txt
│       │   │       │           ├── TestFileTreeDirCursorRight.txt
│       │   │       │           ├── TestFileTreeFilterTree.txt
│       │   │       │           ├── TestFileTreeGoCase.txt
│       │   │       │           ├── TestFileTreeHideAddedRemovedModified.txt
│       │   │       │           ├── TestFileTreeHideTypeWithFilter.txt
│       │   │       │           ├── TestFileTreeHideUnmodified.txt
│       │   │       │           ├── TestFileTreeNoAttributes.txt
│       │   │       │           ├── TestFileTreePageDown.txt
│       │   │       │           ├── TestFileTreePageUp.txt
│       │   │       │           ├── TestFileTreeRestrictedHeight.txt
│       │   │       │           └── TestFileTreeSelectLayer.txt
│       │   │       └── v1.go
│       │   └── testdata/
│       │       ├── config/
│       │       │   └── dive-ci-legacy.yaml
│       │       ├── default-ci-config/
│       │       │   └── .dive-ci
│       │       ├── dive-enable-ci.yaml
│       │       ├── image-multi-layer-containerfile/
│       │       │   ├── Containerfile
│       │       │   ├── dive-pass.yaml
│       │       │   ├── example.md
│       │       │   └── overwrite.md
│       │       ├── image-multi-layer-dockerfile/
│       │       │   ├── Dockerfile
│       │       │   ├── dive-fail.yaml
│       │       │   ├── dive-pass.yaml
│       │       │   ├── example.md
│       │       │   └── overwrite.md
│       │       ├── invalid/
│       │       │   └── Dockerfile
│       │       └── snapshots/
│       │           ├── cli_build_test.snap
│       │           ├── cli_ci_test.snap
│       │           ├── cli_config_test.snap
│       │           ├── cli_json_test.snap
│       │           └── cli_load_test.snap
│       └── main.go
├── dive/
│   ├── filetree/
│   │   ├── comparer.go
│   │   ├── diff.go
│   │   ├── efficiency.go
│   │   ├── efficiency_test.go
│   │   ├── file_info.go
│   │   ├── file_node.go
│   │   ├── file_node_test.go
│   │   ├── file_tree.go
│   │   ├── file_tree_test.go
│   │   ├── node_data.go
│   │   ├── node_data_test.go
│   │   ├── order_strategy.go
│   │   ├── path_error.go
│   │   └── view_info.go
│   ├── get_image_resolver.go
│   └── image/
│       ├── analysis.go
│       ├── docker/
│       │   ├── archive_resolver.go
│       │   ├── build.go
│       │   ├── build_test.go
│       │   ├── cli.go
│       │   ├── config.go
│       │   ├── docker_host_unix.go
│       │   ├── docker_host_windows.go
│       │   ├── engine_resolver.go
│       │   ├── image_archive.go
│       │   ├── image_archive_analysis_test.go
│       │   ├── layer.go
│       │   ├── manifest.go
│       │   └── testing.go
│       ├── image.go
│       ├── layer.go
│       ├── podman/
│       │   ├── build.go
│       │   ├── cli.go
│       │   ├── resolver.go
│       │   └── resolver_unsupported.go
│       └── resolver.go
├── go.mod
├── go.sum
└── internal/
    ├── bus/
    │   ├── bus.go
    │   ├── event/
    │   │   ├── event.go
    │   │   ├── parser/
    │   │   │   └── parsers.go
    │   │   └── payload/
    │   │       ├── explore.go
    │   │       └── generic.go
    │   └── helpers.go
    ├── log/
    │   └── log.go
    └── utils/
        ├── format.go
        └── view.go
Download .txt
SYMBOL INDEX (716 symbols across 110 files)

FILE: .github/scripts/coverage.py
  class bcolors (line 7) | class bcolors:

FILE: cmd/dive/cli/cli.go
  function Application (line 12) | func Application(id clio.Identification) clio.Application {
  function Command (line 17) | func Command(id clio.Identification) *cobra.Command {
  function create (line 22) | func create(id clio.Identification) (clio.Application, *cobra.Command) {

FILE: cmd/dive/cli/cli_build_test.go
  function Test_Build_Dockerfile (line 10) | func Test_Build_Dockerfile(t *testing.T) {
  function Test_Build_Containerfile (line 30) | func Test_Build_Containerfile(t *testing.T) {
  function Test_Build_CI_gate_fail (line 50) | func Test_Build_CI_gate_fail(t *testing.T) {
  function Test_BuildFailure (line 62) | func Test_BuildFailure(t *testing.T) {

FILE: cmd/dive/cli/cli_ci_test.go
  function Test_CI_DefaultCIConfig (line 9) | func Test_CI_DefaultCIConfig(t *testing.T) {
  function Test_CI_Fail (line 27) | func Test_CI_Fail(t *testing.T) {
  function Test_CI_LegacyRules (line 39) | func Test_CI_LegacyRules(t *testing.T) {

FILE: cmd/dive/cli/cli_config_test.go
  function Test_Config (line 8) | func Test_Config(t *testing.T) {

FILE: cmd/dive/cli/cli_json_test.go
  function Test_JsonOutput (line 11) | func Test_JsonOutput(t *testing.T) {

FILE: cmd/dive/cli/cli_load_test.go
  function Test_LoadImage (line 12) | func Test_LoadImage(t *testing.T) {
  function runWithCombinedOutput (line 55) | func runWithCombinedOutput(t testing.TB, cmd string) {
  function assertLoadOutput (line 65) | func assertLoadOutput(t testing.TB, combined string) {
  function Test_FetchFailure (line 73) | func Test_FetchFailure(t *testing.T) {
  function cd (line 97) | func cd(t testing.TB, to string) {

FILE: cmd/dive/cli/cli_test.go
  function TestMain (line 28) | func TestMain(m *testing.M) {
  function TestUpdateSnapshotDisabled (line 49) | func TestUpdateSnapshotDisabled(t *testing.T) {
  function repoPath (line 53) | func repoPath(t testing.TB, path string) string {
  function repoRoot (line 59) | func repoRoot(t testing.TB) string {
  function getTestCommand (line 75) | func getTestCommand(t testing.TB, cmd string) *cobra.Command {
  type capturer (line 102) | type capturer struct
    method WithSuppress (line 112) | func (c *capturer) WithSuppress() *capturer {
    method All (line 117) | func (c *capturer) All() *capturer {
    method WithStdout (line 123) | func (c *capturer) WithStdout() *capturer {
    method WithStderr (line 128) | func (c *capturer) WithStderr() *capturer {
    method Run (line 133) | func (c *capturer) Run(t testing.TB, f func()) string {
  function Capture (line 108) | func Capture() *capturer {

FILE: cmd/dive/cli/internal/command/adapter/analyzer.go
  type Analyzer (line 13) | type Analyzer interface
  type analysisActionObserver (line 17) | type analysisActionObserver struct
    method Analyze (line 27) | func (a analysisActionObserver) Analyze(ctx context.Context, img *imag...
  function NewAnalyzer (line 21) | func NewAnalyzer() Analyzer {

FILE: cmd/dive/cli/internal/command/adapter/evaluator.go
  type Evaluator (line 13) | type Evaluator interface
  type evaluationActionObserver (line 17) | type evaluationActionObserver struct
    method Evaluate (line 27) | func (c evaluationActionObserver) Evaluate(ctx context.Context, analys...
  function NewEvaluator (line 21) | func NewEvaluator(rules []ci.Rule) Evaluator {

FILE: cmd/dive/cli/internal/command/adapter/exporter.go
  type Exporter (line 15) | type Exporter interface
  type jsonExporter (line 19) | type jsonExporter struct
    method ExportTo (line 29) | func (e *jsonExporter) ExportTo(ctx context.Context, analysis *image.A...
  function NewExporter (line 23) | func NewExporter(fs afero.Fs) Exporter {

FILE: cmd/dive/cli/internal/command/adapter/resolver.go
  type imageActionObserver (line 13) | type imageActionObserver struct
    method Build (line 23) | func (i imageActionObserver) Build(ctx context.Context, options []stri...
    method Fetch (line 49) | func (i imageActionObserver) Fetch(ctx context.Context, id string) (*i...
  function ImageResolver (line 17) | func ImageResolver(resolver image.Resolver) image.Resolver {

FILE: cmd/dive/cli/internal/command/build.go
  type buildOptions (line 12) | type buildOptions struct
  function Build (line 18) | func Build(app clio.Application) *cobra.Command {

FILE: cmd/dive/cli/internal/command/ci/evaluator.go
  type Evaluation (line 15) | type Evaluation struct
  type Evaluator (line 20) | type Evaluator struct
    method isRuleEnabled (line 75) | func (e Evaluator) isRuleEnabled(rule Rule) bool {
    method Evaluate (line 79) | func (e Evaluator) Evaluate(ctx context.Context, analysis *image.Analy...
    method report (line 158) | func (e Evaluator) report(analysis *image.Analysis) string {
    method renderAnalysisSection (line 168) | func (e Evaluator) renderAnalysisSection(analysis *image.Analysis) str...
    method renderInefficientFilesSection (line 188) | func (e Evaluator) renderInefficientFilesSection(analysis *image.Analy...
    method renderEvaluationSection (line 212) | func (e Evaluator) renderEvaluationSection() string {
    method formatRuleResult (line 234) | func (e Evaluator) formatRuleResult(ruleName string, result RuleResult...
    method renderStatusSummary (line 260) | func (e Evaluator) renderStatusSummary() string {
  type format (line 30) | type format struct
  type ResultTally (line 42) | type ResultTally struct
  type ReferenceFile (line 50) | type ReferenceFile struct
  function NewEvaluator (line 56) | func NewEvaluator(rules []Rule) Evaluator {
  function formatKeyValue (line 305) | func formatKeyValue(f format, key, value string) string {

FILE: cmd/dive/cli/internal/command/ci/evaluator_test.go
  function Test_Evaluator (line 17) | func Test_Evaluator(t *testing.T) {
  function Test_Evaluator_Misconfigurations (line 107) | func Test_Evaluator_Misconfigurations(t *testing.T) {
  function repoPath (line 178) | func repoPath(t testing.TB, path string) string {
  function repoRoot (line 184) | func repoRoot(t testing.TB) string {

FILE: cmd/dive/cli/internal/command/ci/rule.go
  constant RuleUnknown (line 8) | RuleUnknown = iota
  constant RulePassed (line 9) | RulePassed
  constant RuleFailed (line 10) | RuleFailed
  constant RuleWarning (line 11) | RuleWarning
  constant RuleDisabled (line 12) | RuleDisabled
  constant RuleMisconfigured (line 13) | RuleMisconfigured
  constant RuleConfigured (line 14) | RuleConfigured
  type Rule (line 17) | type Rule interface
  type RuleStatus (line 23) | type RuleStatus
    method String (line 30) | func (status RuleStatus) String(f format) string {
  type RuleResult (line 25) | type RuleResult struct

FILE: cmd/dive/cli/internal/command/ci/rules.go
  constant ciKeyLowestEfficiencyThreshold (line 13) | ciKeyLowestEfficiencyThreshold = "lowestEfficiency"
  constant ciKeyHighestWastedBytes (line 14) | ciKeyHighestWastedBytes        = "highestWastedBytes"
  constant ciKeyHighestUserWastedPercent (line 15) | ciKeyHighestUserWastedPercent  = "highestUserWastedPercent"
  function Rules (line 18) | func Rules(lowerEfficiency, highestWastedBytes, highestUserWastedPercent...
  function DisabledRule (line 44) | func DisabledRule(key string) Rule {
  type BaseRule (line 54) | type BaseRule struct
    method Key (line 61) | func (rule *BaseRule) Key() string {
    method Configuration (line 65) | func (rule *BaseRule) Configuration() string {
    method Evaluate (line 69) | func (rule *BaseRule) Evaluate(result *image.Analysis) (RuleStatus, st...
  type LowestEfficiencyRule (line 77) | type LowestEfficiencyRule struct
    method Evaluate (line 119) | func (r *LowestEfficiencyRule) Evaluate(analysis *image.Analysis) (Rul...
  type HighestWastedBytesRule (line 83) | type HighestWastedBytesRule struct
    method Evaluate (line 149) | func (r *HighestWastedBytesRule) Evaluate(analysis *image.Analysis) (R...
  type HighestUserWastedPercentRule (line 89) | type HighestUserWastedPercentRule struct
    method Evaluate (line 184) | func (r *HighestUserWastedPercentRule) Evaluate(analysis *image.Analys...
  function NewLowestEfficiencyRule (line 94) | func NewLowestEfficiencyRule(configValue string) (Rule, error) {
  function NewHighestWastedBytesRule (line 129) | func NewHighestWastedBytesRule(configValue string) (Rule, error) {
  function NewHighestUserWastedPercentRule (line 159) | func NewHighestUserWastedPercentRule(configValue string) (Rule, error) {
  function isRuleDisabled (line 193) | func isRuleDisabled(value string) bool {

FILE: cmd/dive/cli/internal/command/export/export.go
  type Export (line 10) | type Export struct
    method Marshal (line 84) | func (exp *Export) Marshal() ([]byte, error) {
  type Layer (line 15) | type Layer struct
  type Image (line 24) | type Image struct
  type FileReference (line 31) | type FileReference struct
  function NewExport (line 38) | func NewExport(analysis *diveImage.Analysis) *Export {

FILE: cmd/dive/cli/internal/command/export/export_test.go
  function Test_Export (line 9) | func Test_Export(t *testing.T) {

FILE: cmd/dive/cli/internal/command/export/main_test.go
  function TestMain (line 23) | func TestMain(m *testing.M) {
  function TestUpdateSnapshotDisabled (line 44) | func TestUpdateSnapshotDisabled(t *testing.T) {
  function repoPath (line 48) | func repoPath(t testing.TB, path string) string {
  function repoRoot (line 54) | func repoRoot(t testing.TB) string {

FILE: cmd/dive/cli/internal/command/root.go
  type rootOptions (line 19) | type rootOptions struct
  function Root (line 25) | func Root(app clio.Application) *cobra.Command {
  function setUI (line 63) | func setUI(app clio.Application, opts options.Application) error {
  function run (line 74) | func run(ctx context.Context, opts options.Application, img *image.Image...

FILE: cmd/dive/cli/internal/options/analysis.go
  constant defaultContainerEngine (line 12) | defaultContainerEngine = "docker"
  type Analysis (line 20) | type Analysis struct
    method DescribeFields (line 36) | func (c *Analysis) DescribeFields(descriptions clio.FieldDescriptionSe...
    method AddFlags (line 41) | func (c *Analysis) AddFlags(flags clio.FlagSet) {
    method PostLoad (line 48) | func (c *Analysis) PostLoad() error {
  function DefaultAnalysis (line 28) | func DefaultAnalysis() Analysis {

FILE: cmd/dive/cli/internal/options/application.go
  type Application (line 7) | type Application struct
    method V1Preferences (line 23) | func (c Application) V1Preferences() v1.Preferences {
  function DefaultApplication (line 14) | func DefaultApplication() Application {

FILE: cmd/dive/cli/internal/options/ci.go
  constant defaultCIConfigPath (line 16) | defaultCIConfigPath = ".dive-ci"
  type CI (line 18) | type CI struct
    method DescribeFields (line 32) | func (c *CI) DescribeFields(descriptions clio.FieldDescriptionSet) {
    method AddFlags (line 37) | func (c *CI) AddFlags(flags clio.FlagSet) {
    method PostLoad (line 42) | func (c *CI) PostLoad() error {
  function DefaultCI (line 24) | func DefaultCI() CI {
  type legacyRuleFile (line 83) | type legacyRuleFile struct
  function fileExists (line 89) | func fileExists(path string) bool {
  function truthy (line 96) | func truthy(value string) bool {

FILE: cmd/dive/cli/internal/options/ci_rules.go
  type CIRules (line 9) | type CIRules struct
    method DescribeFields (line 30) | func (c *CIRules) DescribeFields(descriptions clio.FieldDescriptionSet) {
    method AddFlags (line 36) | func (c *CIRules) AddFlags(flags clio.FlagSet) {
    method hasLegacyOptionsInUse (line 42) | func (c CIRules) hasLegacyOptionsInUse() bool {
    method PostLoad (line 46) | func (c *CIRules) PostLoad() error {
  function DefaultCIRules (line 22) | func DefaultCIRules() CIRules {

FILE: cmd/dive/cli/internal/options/export.go
  type Export (line 17) | type Export struct
    method AddFlags (line 26) | func (o *Export) AddFlags(flags clio.FlagSet) {
    method PostLoad (line 30) | func (o *Export) PostLoad() error {
  function DefaultExport (line 22) | func DefaultExport() Export {

FILE: cmd/dive/cli/internal/options/ui.go
  type UI (line 4) | type UI struct
  function DefaultUI (line 11) | func DefaultUI() UI {

FILE: cmd/dive/cli/internal/options/ui_diff.go
  type UIDiff (line 15) | type UIDiff struct
    method DescribeFields (line 26) | func (c *UIDiff) DescribeFields(descriptions clio.FieldDescriptionSet) {
    method PostLoad (line 30) | func (c *UIDiff) PostLoad() error {
  function DefaultUIDiff (line 19) | func DefaultUIDiff() UIDiff {

FILE: cmd/dive/cli/internal/options/ui_filetree.go
  type UIFiletree (line 15) | type UIFiletree struct
    method DescribeFields (line 30) | func (c *UIFiletree) DescribeFields(descriptions clio.FieldDescription...
    method PostLoad (line 36) | func (c *UIFiletree) PostLoad() error {
  function DefaultUIFiletree (line 21) | func DefaultUIFiletree() UIFiletree {

FILE: cmd/dive/cli/internal/options/ui_keybindings.go
  type UIKeybindings (line 14) | type UIKeybindings struct
    method PostLoad (line 95) | func (c *UIKeybindings) PostLoad() error {
    method DescribeFields (line 149) | func (c *UIKeybindings) DescribeFields(descriptions clio.FieldDescript...
  type GlobalBindings (line 23) | type GlobalBindings struct
  type NavigationBindings (line 30) | type NavigationBindings struct
  type LayerBindings (line 39) | type LayerBindings struct
  type FiletreeBindings (line 44) | type FiletreeBindings struct
  function DefaultUIKeybinding (line 57) | func DefaultUIKeybinding() UIKeybindings {
  function getUIBindingValues (line 67) | func getUIBindingValues(src, dst reflect.Value) {
  function createKeyBindings (line 108) | func createKeyBindings(src, dst reflect.Value) error {

FILE: cmd/dive/cli/internal/options/ui_layers.go
  type UILayers (line 8) | type UILayers struct
    method DescribeFields (line 18) | func (c *UILayers) DescribeFields(descriptions clio.FieldDescriptionSe...
  function DefaultUILayers (line 12) | func DefaultUILayers() UILayers {

FILE: cmd/dive/cli/internal/ui/no_ui.go
  type NoUI (line 11) | type NoUI struct
    method Setup (line 19) | func (n *NoUI) Setup(subscription partybus.Unsubscribable) error {
    method Handle (line 24) | func (n *NoUI) Handle(_ partybus.Event) error {
    method Teardown (line 28) | func (n NoUI) Teardown(_ bool) error {
  function None (line 15) | func None() *NoUI {

FILE: cmd/dive/cli/internal/ui/v1.go
  type V1UI (line 23) | type V1UI struct
    method Setup (line 56) | func (n *V1UI) Setup(subscription partybus.Unsubscribable) error {
    method Handle (line 93) | func (n *V1UI) Handle(e partybus.Event) error {
    method writeToStdout (line 161) | func (n *V1UI) writeToStdout(s string) {
    method writeToStderr (line 165) | func (n *V1UI) writeToStderr(s string) {
    method Teardown (line 174) | func (n V1UI) Teardown(_ bool) error {
  type format (line 34) | type format struct
  function NewV1UI (line 41) | func NewV1UI(cfg v1.Preferences, out io.Writer, quiet bool, verbosity in...
  type environWithoutCI (line 72) | type environWithoutCI struct
    method Environ (line 75) | func (e environWithoutCI) Environ() []string {
    method Getenv (line 86) | func (e environWithoutCI) Getenv(s string) string {

FILE: cmd/dive/cli/internal/ui/v1/app/app.go
  constant debug (line 14) | debug = false
  type app (line 16) | type app struct
    method quit (line 134) | func (a *app) quit() error {
  function Run (line 23) | func Run(ctx context.Context, c v1.Config) error {
  function newApp (line 55) | func newApp(ctx context.Context, gui *gocui.Gui, cfg v1.Config) (*app, e...

FILE: cmd/dive/cli/internal/ui/v1/app/controller.go
  type controller (line 14) | type controller struct
    method onFileTreeViewExtract (line 62) | func (c *controller) onFileTreeViewExtract(p string) error {
    method onFileTreeViewOptionChange (line 66) | func (c *controller) onFileTreeViewOptionChange() error {
    method onFilterEdit (line 74) | func (c *controller) onFilterEdit(filter string) error {
    method onLayerChange (line 95) | func (c *controller) onLayerChange(selection viewmodel.LayerSelection)...
    method UpdateAndRender (line 115) | func (c *controller) UpdateAndRender() error {
    method Update (line 130) | func (c *controller) Update() error {
    method Render (line 141) | func (c *controller) Render() error {
    method NextPane (line 154) | func (c *controller) NextPane() (err error) {
    method PrevPane (line 178) | func (c *controller) PrevPane() (err error) {
    method ToggleView (line 202) | func (c *controller) ToggleView() (err error) {
    method CloseFilterView (line 219) | func (c *controller) CloseFilterView() error {
    method ToggleFilterView (line 228) | func (c *controller) ToggleFilterView() error {
  function newController (line 21) | func newController(ctx context.Context, g *gocui.Gui, cfg v1.Config) (*c...

FILE: cmd/dive/cli/internal/ui/v1/app/job_control_other.go
  function handle_ctrl_z (line 11) | func handle_ctrl_z(_ *gocui.Gui, _ *gocui.View) error {

FILE: cmd/dive/cli/internal/ui/v1/app/job_control_unix.go
  function handle_ctrl_z (line 13) | func handle_ctrl_z(g *gocui.Gui, v *gocui.View) error {

FILE: cmd/dive/cli/internal/ui/v1/config.go
  type Config (line 13) | type Config struct
    method TreeComparer (line 45) | func (c *Config) TreeComparer() (filetree.Comparer, error) {
  type Preferences (line 24) | type Preferences struct
  function DefaultPreferences (line 34) | func DefaultPreferences() Preferences {
  type ContentReader (line 65) | type ContentReader interface

FILE: cmd/dive/cli/internal/ui/v1/format/format.go
  constant selectedLeftBracketStr (line 28) | selectedLeftBracketStr  = "┃"
  constant selectedRightBracketStr (line 29) | selectedRightBracketStr = "┣"
  constant selectedFillStr (line 30) | selectedFillStr         = "━"
  constant leftBracketStr (line 32) | leftBracketStr  = "│"
  constant rightBracketStr (line 33) | rightBracketStr = "├"
  constant fillStr (line 34) | fillStr         = "─"
  constant selectStr (line 36) | selectStr = " ● "
  function init (line 52) | func init() {
  function RenderNoHeader (line 70) | func RenderNoHeader(width int, selected bool) string {
  function RenderHeader (line 77) | func RenderHeader(title string, width int, selected bool) string {
  function RenderHelpKey (line 98) | func RenderHelpKey(control, title string, selected bool) string {

FILE: cmd/dive/cli/internal/ui/v1/key/binding.go
  type BindingInfo (line 10) | type BindingInfo struct
  type Binding (line 19) | type Binding struct
    method RegisterSelectionFn (line 65) | func (binding *Binding) RegisterSelectionFn(selectedFn func() bool) {
    method onAction (line 69) | func (binding *Binding) onAction(*gocui.Gui, *gocui.View) error {
    method isSelected (line 76) | func (binding *Binding) isSelected() bool {
    method RenderKeyHelp (line 84) | func (binding *Binding) RenderKeyHelp() string {
  function GenerateBindings (line 26) | func GenerateBindings(gui *gocui.Gui, influence string, infos []BindingI...
  function newBinding (line 49) | func newBinding(gui *gocui.Gui, influence string, keys []keybinding.Key,...

FILE: cmd/dive/cli/internal/ui/v1/key/config.go
  type Config (line 8) | type Config struct
    method Setup (line 13) | func (c *Config) Setup() error {
  type Bindings (line 26) | type Bindings struct
  type GlobalBindings (line 33) | type GlobalBindings struct
  type NavigationBindings (line 40) | type NavigationBindings struct
  type LayerBindings (line 49) | type LayerBindings struct
  type FiletreeBindings (line 54) | type FiletreeBindings struct
  function DefaultBindings (line 67) | func DefaultBindings() Bindings {

FILE: cmd/dive/cli/internal/ui/v1/layout/area.go
  type Area (line 3) | type Area struct

FILE: cmd/dive/cli/internal/ui/v1/layout/compound/layer_details_column.go
  type LayerDetailsCompoundLayout (line 11) | type LayerDetailsCompoundLayout struct
    method Name (line 26) | func (cl *LayerDetailsCompoundLayout) Name() string {
    method OnLayoutChange (line 31) | func (cl *LayerDetailsCompoundLayout) OnLayoutChange() error {
    method layoutRow (line 49) | func (cl *LayerDetailsCompoundLayout) layoutRow(g *gocui.Gui, minX, mi...
    method Layout (line 70) | func (cl *LayerDetailsCompoundLayout) Layout(g *gocui.Gui, minX, minY,...
    method RequestedSize (line 94) | func (cl *LayerDetailsCompoundLayout) RequestedSize(available int) *int {
    method IsVisible (line 109) | func (cl *LayerDetailsCompoundLayout) IsVisible() bool {
  function NewLayerDetailsCompoundLayout (line 18) | func NewLayerDetailsCompoundLayout(layer *view.Layer, layerDetails *view...

FILE: cmd/dive/cli/internal/ui/v1/layout/layout.go
  type Layout (line 5) | type Layout interface

FILE: cmd/dive/cli/internal/ui/v1/layout/location.go
  constant LocationFooter (line 4) | LocationFooter Location = iota
  constant LocationHeader (line 5) | LocationHeader
  constant LocationColumn (line 6) | LocationColumn
  type Location (line 9) | type Location

FILE: cmd/dive/cli/internal/ui/v1/layout/manager.go
  type Constraint (line 9) | type Constraint
  type Manager (line 11) | type Manager struct
    method Add (line 23) | func (lm *Manager) Add(element Layout, location Location) {
    method planAndLayoutHeaders (line 30) | func (lm *Manager) planAndLayoutHeaders(g *gocui.Gui, area Area) (Area...
    method planFooters (line 60) | func (lm *Manager) planFooters(g *gocui.Gui, area Area) (Area, []int) {
    method planAndLayoutColumns (line 92) | func (lm *Manager) planAndLayoutColumns(g *gocui.Gui, area Area) (Area...
    method layoutFooters (line 148) | func (lm *Manager) layoutFooters(g *gocui.Gui, area Area, footerHeight...
    method notifyLayoutChange (line 174) | func (lm *Manager) notifyLayoutChange() error {
    method Layout (line 186) | func (lm *Manager) Layout(g *gocui.Gui) error {
    method layout (line 198) | func (lm *Manager) layout(g *gocui.Gui, curMaxX, curMaxY int) error {
  function NewManager (line 17) | func NewManager() *Manager {

FILE: cmd/dive/cli/internal/ui/v1/layout/manager_test.go
  type testElement (line 9) | type testElement struct
    method Name (line 25) | func (te *testElement) Name() string {
    method Layout (line 28) | func (te *testElement) Layout(g *gocui.Gui, minX, minY, maxX, maxY int...
    method RequestedSize (line 41) | func (te *testElement) RequestedSize(available int) *int {
    method IsVisible (line 47) | func (te *testElement) IsVisible() bool {
    method OnLayoutChange (line 50) | func (te *testElement) OnLayoutChange() error {
  function newTestElement (line 16) | func newTestElement(t *testing.T, size int, layoutArea Area, location Lo...
  type layoutReturn (line 54) | type layoutReturn struct
  function Test_planAndLayoutHeaders (line 59) | func Test_planAndLayoutHeaders(t *testing.T) {
  function Test_planAndLayoutColumns (line 159) | func Test_planAndLayoutColumns(t *testing.T) {
  function Test_layout (line 259) | func Test_layout(t *testing.T) {

FILE: cmd/dive/cli/internal/ui/v1/view/cursor.go
  function CursorDown (line 10) | func CursorDown(g *gocui.Gui, v *gocui.View) error {
  function CursorUp (line 15) | func CursorUp(g *gocui.Gui, v *gocui.View) error {
  function CursorStep (line 20) | func CursorStep(g *gocui.Gui, v *gocui.View, step int) error {

FILE: cmd/dive/cli/internal/ui/v1/view/debug.go
  type Debug (line 13) | type Debug struct
    method SetCurrentView (line 35) | func (v *Debug) SetCurrentView(r Helper) {
    method Name (line 39) | func (v *Debug) Name() string {
    method Setup (line 44) | func (v *Debug) Setup(view *gocui.View, header *gocui.View) error {
    method IsVisible (line 62) | func (v *Debug) IsVisible() bool {
    method Update (line 67) | func (v *Debug) Update() error {
    method OnLayoutChange (line 72) | func (v *Debug) OnLayoutChange() error {
    method Render (line 81) | func (v *Debug) Render() error {
    method Layout (line 103) | func (v *Debug) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
    method RequestedSize (line 122) | func (v *Debug) RequestedSize(available int) *int {
  function newDebugView (line 24) | func newDebugView(gui *gocui.Gui) *Debug {

FILE: cmd/dive/cli/internal/ui/v1/view/filetree.go
  type ViewOptionChangeListener (line 18) | type ViewOptionChangeListener
  type ViewExtractListener (line 20) | type ViewExtractListener
  type FileTree (line 24) | type FileTree struct
    method AddViewOptionChangeListener (line 67) | func (v *FileTree) AddViewOptionChangeListener(listener ...ViewOptionC...
    method AddViewExtractListener (line 71) | func (v *FileTree) AddViewExtractListener(listener ...ViewExtractListe...
    method SetTitle (line 75) | func (v *FileTree) SetTitle(title string) {
    method SetFilterRegex (line 79) | func (v *FileTree) SetFilterRegex(filterRegex *regexp.Regexp) {
    method Name (line 83) | func (v *FileTree) Name() string {
    method Setup (line 88) | func (v *FileTree) Setup(view, header *gocui.View) error {
    method IsVisible (line 204) | func (v *FileTree) IsVisible() bool {
    method resetCursor (line 209) | func (v *FileTree) resetCursor() {
    method SetTree (line 215) | func (v *FileTree) SetTree(bottomTreeStart, bottomTreeStop, topTreeSta...
    method CursorDown (line 229) | func (v *FileTree) CursorDown() error {
    method CursorUp (line 240) | func (v *FileTree) CursorUp() error {
    method CursorLeft (line 248) | func (v *FileTree) CursorLeft() error {
    method CursorRight (line 258) | func (v *FileTree) CursorRight() error {
    method PageDown (line 268) | func (v *FileTree) PageDown() error {
    method PageUp (line 277) | func (v *FileTree) PageUp() error {
    method toggleCollapse (line 291) | func (v *FileTree) toggleCollapse() error {
    method toggleCollapseAll (line 301) | func (v *FileTree) toggleCollapseAll() error {
    method toggleSortOrder (line 313) | func (v *FileTree) toggleSortOrder() error {
    method extractFile (line 323) | func (v *FileTree) extractFile() error {
    method toggleWrapTree (line 335) | func (v *FileTree) toggleWrapTree() error {
    method notifyOnViewOptionChangeListeners (line 351) | func (v *FileTree) notifyOnViewOptionChangeListeners() error {
    method toggleAttributes (line 362) | func (v *FileTree) toggleAttributes() error {
    method toggleShowDiffType (line 382) | func (v *FileTree) toggleShowDiffType(diffType filetree.DiffType) error {
    method OnLayoutChange (line 399) | func (v *FileTree) OnLayoutChange() error {
    method Update (line 408) | func (v *FileTree) Update() error {
    method Render (line 422) | func (v *FileTree) Render() error {
    method KeyHelp (line 452) | func (v *FileTree) KeyHelp() string {
    method Layout (line 460) | func (v *FileTree) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) er...
    method RequestedSize (line 492) | func (v *FileTree) RequestedSize(available int) *int {
  function newFileTreeView (line 42) | func newFileTreeView(gui *gocui.Gui, cfg v1.Config, initial int) (v *Fil...

FILE: cmd/dive/cli/internal/ui/v1/view/filter.go
  type FilterEditListener (line 14) | type FilterEditListener
  type Filter (line 18) | type Filter struct
    method AddFilterEditListener (line 49) | func (v *Filter) AddFilterEditListener(listener ...FilterEditListener) {
    method Name (line 53) | func (v *Filter) Name() string {
    method Setup (line 58) | func (v *Filter) Setup(view, header *gocui.View) error {
    method ToggleVisible (line 79) | func (v *Filter) ToggleVisible() error {
    method IsVisible (line 101) | func (v *Filter) IsVisible() bool {
    method Edit (line 109) | func (v *Filter) Edit(view *gocui.View, key gocui.Key, ch rune, mod go...
    method notifyFilterEditListeners (line 130) | func (v *Filter) notifyFilterEditListeners() {
    method Update (line 142) | func (v *Filter) Update() error {
    method Render (line 147) | func (v *Filter) Render() error {
    method KeyHelp (line 158) | func (v *Filter) KeyHelp() string {
    method OnLayoutChange (line 163) | func (v *Filter) OnLayoutChange() error {
    method Layout (line 171) | func (v *Filter) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
    method RequestedSize (line 186) | func (v *Filter) RequestedSize(available int) *int {
  function newFilterView (line 33) | func newFilterView(gui *gocui.Gui) *Filter {

FILE: cmd/dive/cli/internal/ui/v1/view/image_details.go
  type ImageDetails (line 17) | type ImageDetails struct
    method Name (line 30) | func (v *ImageDetails) Name() string {
    method Setup (line 34) | func (v *ImageDetails) Setup(body, header *gocui.View) error {
    method Render (line 82) | func (v *ImageDetails) Render() error {
    method OnLayoutChange (line 130) | func (v *ImageDetails) OnLayoutChange() error {
    method IsVisible (line 138) | func (v *ImageDetails) IsVisible() bool {
    method PageUp (line 142) | func (v *ImageDetails) PageUp() error {
    method PageDown (line 150) | func (v *ImageDetails) PageDown() error {
    method CursorUp (line 158) | func (v *ImageDetails) CursorUp() error {
    method CursorDown (line 165) | func (v *ImageDetails) CursorDown() error {
    method KeyHelp (line 173) | func (v *ImageDetails) KeyHelp() string {
    method Update (line 178) | func (v *ImageDetails) Update() error {

FILE: cmd/dive/cli/internal/ui/v1/view/layer.go
  type Layer (line 17) | type Layer struct
    method AddLayerChangeListener (line 60) | func (v *Layer) AddLayerChangeListener(listener ...LayerChangeListener) {
    method notifyLayerChangeListeners (line 64) | func (v *Layer) notifyLayerChangeListeners() error {
    method Name (line 88) | func (v *Layer) Name() string {
    method Setup (line 93) | func (v *Layer) Setup(body *gocui.View, header *gocui.View) error {
    method height (line 150) | func (v *Layer) height() uint {
    method CompareMode (line 155) | func (v *Layer) CompareMode() viewmodel.LayerCompareMode {
    method IsVisible (line 160) | func (v *Layer) IsVisible() bool {
    method PageDown (line 165) | func (v *Layer) PageDown() error {
    method PageUp (line 184) | func (v *Layer) PageUp() error {
    method CursorDown (line 203) | func (v *Layer) CursorDown() error {
    method CursorUp (line 215) | func (v *Layer) CursorUp() error {
    method SetOrigin (line 227) | func (v *Layer) SetOrigin(x, y int) error {
    method SetCursor (line 235) | func (v *Layer) SetCursor(layer int) error {
    method CurrentLayer (line 246) | func (v *Layer) CurrentLayer() *image.Layer {
    method setCompareMode (line 251) | func (v *Layer) setCompareMode(compareMode viewmodel.LayerCompareMode)...
    method renderCompareBar (line 257) | func (v *Layer) renderCompareBar(layerIdx int) string {
    method ConstrainLayout (line 271) | func (v *Layer) ConstrainLayout() {
    method ExpandLayout (line 278) | func (v *Layer) ExpandLayout() {
    method OnLayoutChange (line 286) | func (v *Layer) OnLayoutChange() error {
    method Update (line 295) | func (v *Layer) Update() error {
    method Render (line 302) | func (v *Layer) Render() error {
    method LayerCount (line 366) | func (v *Layer) LayerCount() int {
    method KeyHelp (line 371) | func (v *Layer) KeyHelp() string {
  function newLayerView (line 33) | func newLayerView(gui *gocui.Gui, cfg v1.Config) (c *Layer, err error) {

FILE: cmd/dive/cli/internal/ui/v1/view/layer_change_listener.go
  type LayerChangeListener (line 7) | type LayerChangeListener

FILE: cmd/dive/cli/internal/ui/v1/view/layer_details.go
  type LayerDetails (line 16) | type LayerDetails struct
    method Name (line 25) | func (v *LayerDetails) Name() string {
    method Setup (line 29) | func (v *LayerDetails) Setup(body, header *gocui.View) error {
    method Render (line 71) | func (v *LayerDetails) Render() error {
    method OnLayoutChange (line 109) | func (v *LayerDetails) OnLayoutChange() error {
    method IsVisible (line 117) | func (v *LayerDetails) IsVisible() bool {
    method CursorUp (line 122) | func (v *LayerDetails) CursorUp() error {
    method CursorDown (line 130) | func (v *LayerDetails) CursorDown() error {
    method KeyHelp (line 138) | func (v *LayerDetails) KeyHelp() string {
    method Update (line 143) | func (v *LayerDetails) Update() error {
    method SetCursor (line 147) | func (v *LayerDetails) SetCursor(x, y int) error {

FILE: cmd/dive/cli/internal/ui/v1/view/renderer.go
  type Renderer (line 4) | type Renderer interface
  type Helper (line 10) | type Helper interface

FILE: cmd/dive/cli/internal/ui/v1/view/status.go
  type Status (line 17) | type Status struct
    method SetCurrentView (line 43) | func (v *Status) SetCurrentView(r Helper) {
    method Name (line 47) | func (v *Status) Name() string {
    method AddHelpKeys (line 51) | func (v *Status) AddHelpKeys(keys ...*key.Binding) {
    method Setup (line 56) | func (v *Status) Setup(view *gocui.View) error {
    method IsVisible (line 67) | func (v *Status) IsVisible() bool {
    method Update (line 72) | func (v *Status) Update() error {
    method OnLayoutChange (line 77) | func (v *Status) OnLayoutChange() error {
    method Render (line 86) | func (v *Status) Render() error {
    method KeyHelp (line 108) | func (v *Status) KeyHelp() string {
    method Layout (line 116) | func (v *Status) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
    method RequestedSize (line 129) | func (v *Status) RequestedSize(available int) *int {
  function newStatusView (line 30) | func newStatusView(gui *gocui.Gui) *Status {

FILE: cmd/dive/cli/internal/ui/v1/view/views.go
  type View (line 8) | type View interface
  type Views (line 14) | type Views struct
    method Renderers (line 58) | func (views *Views) Renderers() []Renderer {
  function NewViews (line 24) | func NewViews(g *gocui.Gui, cfg v1.Config) (*Views, error) {

FILE: cmd/dive/cli/internal/ui/v1/viewmodel/filetree.go
  type FileTreeViewModel (line 18) | type FileTreeViewModel struct
    method Setup (line 78) | func (vm *FileTreeViewModel) Setup(lowerBound, height int) {
    method height (line 84) | func (vm *FileTreeViewModel) height() int {
    method bufferIndexUpperBound (line 92) | func (vm *FileTreeViewModel) bufferIndexUpperBound() int {
    method IsVisible (line 97) | func (vm *FileTreeViewModel) IsVisible() bool {
    method ResetCursor (line 102) | func (vm *FileTreeViewModel) ResetCursor() {
    method SetTreeByLayer (line 109) | func (vm *FileTreeViewModel) SetTreeByLayer(bottomTreeStart, bottomTre...
    method CursorUp (line 136) | func (vm *FileTreeViewModel) CursorUp() bool {
    method CursorDown (line 151) | func (vm *FileTreeViewModel) CursorDown() bool {
    method CurrentNode (line 166) | func (vm *FileTreeViewModel) CurrentNode(filterRegex *regexp.Regexp) *...
    method CursorLeft (line 171) | func (vm *FileTreeViewModel) CursorLeft(filterRegex *regexp.Regexp) er...
    method CursorRight (line 221) | func (vm *FileTreeViewModel) CursorRight(filterRegex *regexp.Regexp) e...
    method PageDown (line 253) | func (vm *FileTreeViewModel) PageDown() error {
    method PageUp (line 279) | func (vm *FileTreeViewModel) PageUp() error {
    method getAbsPositionNode (line 304) | func (vm *FileTreeViewModel) getAbsPositionNode(filterRegex *regexp.Re...
    method ToggleCollapse (line 339) | func (vm *FileTreeViewModel) ToggleCollapse(filterRegex *regexp.Regexp...
    method ToggleCollapseAll (line 348) | func (vm *FileTreeViewModel) ToggleCollapseAll() error {
    method ToggleSortOrder (line 369) | func (vm *FileTreeViewModel) ToggleSortOrder() error {
    method ConstrainLayout (line 375) | func (vm *FileTreeViewModel) ConstrainLayout() {
    method ExpandLayout (line 383) | func (vm *FileTreeViewModel) ExpandLayout() {
    method ToggleAttributes (line 391) | func (vm *FileTreeViewModel) ToggleAttributes() error {
    method ToggleShowDiffType (line 401) | func (vm *FileTreeViewModel) ToggleShowDiffType(diffType filetree.Diff...
    method Update (line 406) | func (vm *FileTreeViewModel) Update(filterRegex *regexp.Regexp, width,...
    method Render (line 452) | func (vm *FileTreeViewModel) Render() error {
  function NewFileTreeViewModel (line 41) | func NewFileTreeViewModel(cfg v1.Config, initialLayer int) (treeViewMode...

FILE: cmd/dive/cli/internal/ui/v1/viewmodel/filetree_test.go
  function TestUpdateSnapshotDisabled (line 25) | func TestUpdateSnapshotDisabled(t *testing.T) {
  function fileExists (line 29) | func fileExists(filename string) bool {
  function testCaseDataFilePath (line 37) | func testCaseDataFilePath(name string) string {
  function helperLoadBytes (line 41) | func helperLoadBytes(t *testing.T) []byte {
  function helperCaptureBytes (line 51) | func helperCaptureBytes(t *testing.T, data []byte) {
  function initializeTestViewModel (line 66) | func initializeTestViewModel(t *testing.T) *FileTreeViewModel {
  function runTestCase (line 81) | func runTestCase(t *testing.T, vm *FileTreeViewModel, width, height int,...
  function checkError (line 108) | func checkError(t *testing.T, err error, message string) {
  function TestFileTreeGoCase (line 114) | func TestFileTreeGoCase(t *testing.T) {
  function TestFileTreeNoAttributes (line 124) | func TestFileTreeNoAttributes(t *testing.T) {
  function TestFileTreeRestrictedHeight (line 134) | func TestFileTreeRestrictedHeight(t *testing.T) {
  function TestFileTreeDirCollapse (line 144) | func TestFileTreeDirCollapse(t *testing.T) {
  function assertPath (line 174) | func assertPath(t *testing.T, vm *FileTreeViewModel, expected string, ms...
  function TestFileTreeDirCollapseAll (line 181) | func TestFileTreeDirCollapseAll(t *testing.T) {
  function TestFileTreeSelectLayer (line 194) | func TestFileTreeSelectLayer(t *testing.T) {
  function TestFileShowAggregateChanges (line 213) | func TestFileShowAggregateChanges(t *testing.T) {
  function TestFileTreePageDown (line 231) | func TestFileTreePageDown(t *testing.T) {
  function TestFileTreePageUp (line 252) | func TestFileTreePageUp(t *testing.T) {
  function TestFileTreeDirCursorRight (line 275) | func TestFileTreeDirCursorRight(t *testing.T) {
  function TestFileTreeFilterTree (line 307) | func TestFileTreeFilterTree(t *testing.T) {
  function TestFileTreeHideAddedRemovedModified (line 322) | func TestFileTreeHideAddedRemovedModified(t *testing.T) {
  function TestFileTreeHideUnmodified (line 351) | func TestFileTreeHideUnmodified(t *testing.T) {
  function TestFileTreeHideTypeWithFilter (line 374) | func TestFileTreeHideTypeWithFilter(t *testing.T) {
  function repoPath (line 402) | func repoPath(t testing.TB, path string) string {
  function repoRoot (line 408) | func repoRoot(t testing.TB) string {

FILE: cmd/dive/cli/internal/ui/v1/viewmodel/layer_compare.go
  constant CompareSingleLayer (line 4) | CompareSingleLayer LayerCompareMode = iota
  constant CompareAllLayers (line 5) | CompareAllLayers
  type LayerCompareMode (line 8) | type LayerCompareMode

FILE: cmd/dive/cli/internal/ui/v1/viewmodel/layer_selection.go
  type LayerSelection (line 7) | type LayerSelection struct

FILE: cmd/dive/cli/internal/ui/v1/viewmodel/layer_set_state.go
  type LayerSetState (line 5) | type LayerSetState struct
    method GetCompareIndexes (line 20) | func (state *LayerSetState) GetCompareIndexes() (bottomTreeStart, bott...
  function NewLayerSetState (line 12) | func NewLayerSetState(layers []*image.Layer, compareMode LayerCompareMod...

FILE: cmd/dive/cli/internal/ui/v1/viewmodel/layer_set_state_test.go
  function TestGetCompareIndexes (line 7) | func TestGetCompareIndexes(t *testing.T) {

FILE: cmd/dive/main.go
  constant applicationName (line 30) | applicationName = "dive"
  constant notProvided (line 31) | notProvided     = "[not provided]"
  function main (line 43) | func main() {

FILE: dive/filetree/comparer.go
  type TreeIndexKey (line 7) | type TreeIndexKey struct
    method String (line 20) | func (index TreeIndexKey) String() string {
  function NewTreeIndexKey (line 11) | func NewTreeIndexKey(bottomTreeStart, bottomTreeStop, topTreeStart, topT...
  type Comparer (line 31) | type Comparer struct
    method GetPathErrors (line 45) | func (cmp *Comparer) GetPathErrors(key TreeIndexKey) ([]PathError, err...
    method GetTree (line 53) | func (cmp *Comparer) GetTree(key TreeIndexKey) (*FileTree, error) {
    method get (line 70) | func (cmp *Comparer) get(key TreeIndexKey) (*FileTree, []PathError, er...
    method NaturalIndexes (line 86) | func (cmp *Comparer) NaturalIndexes() <-chan TreeIndexKey {
    method AggregatedIndexes (line 118) | func (cmp *Comparer) AggregatedIndexes() <-chan TreeIndexKey {
    method BuildCache (line 148) | func (cmp *Comparer) BuildCache() (errors []error) {
  function NewComparer (line 37) | func NewComparer(refTrees []*FileTree) Comparer {

FILE: dive/filetree/diff.go
  constant Unmodified (line 8) | Unmodified DiffType = iota
  constant Modified (line 9) | Modified
  constant Added (line 10) | Added
  constant Removed (line 11) | Removed
  type DiffType (line 15) | type DiffType
    method String (line 18) | func (diff DiffType) String() string {
    method merge (line 35) | func (diff DiffType) merge(other DiffType) DiffType {

FILE: dive/filetree/efficiency.go
  type EfficiencyData (line 10) | type EfficiencyData struct
  type EfficiencySlice (line 18) | type EfficiencySlice
    method Len (line 21) | func (efs EfficiencySlice) Len() int {
    method Swap (line 26) | func (efs EfficiencySlice) Swap(i, j int) {
    method Less (line 31) | func (efs EfficiencySlice) Less(i, j int) bool {
  function Efficiency (line 38) | func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {

FILE: dive/filetree/efficiency_test.go
  function checkError (line 7) | func checkError(t *testing.T, err error, message string) {
  function TestEfficiency (line 13) | func TestEfficiency(t *testing.T) {
  function TestEfficiency_ScratchImage (line 59) | func TestEfficiency_ScratchImage(t *testing.T) {

FILE: dive/filetree/file_info.go
  type FileInfo (line 13) | type FileInfo struct
    method Copy (line 93) | func (data *FileInfo) Copy() *FileInfo {
    method Compare (line 111) | func (data *FileInfo) Compare(other FileInfo) DiffType {
  function NewFileInfoFromTarHeader (line 26) | func NewFileInfoFromTarHeader(reader *tar.Reader, header *tar.Header, pa...
  function NewFileInfo (line 45) | func NewFileInfo(realPath, path string, info os.FileInfo) FileInfo {
  function getHashFromReader (line 123) | func getHashFromReader(reader io.Reader) uint64 {

FILE: dive/filetree/file_node.go
  constant AttributeFormat (line 15) | AttributeFormat = "%s%s %11s %10s "
  type FileNode (line 26) | type FileNode struct
    method renderTreeLine (line 54) | func (node *FileNode) renderTreeLine(spaces []bool, last bool, collaps...
    method Copy (line 78) | func (node *FileNode) Copy(parent *FileNode) *FileNode {
    method AddChild (line 90) | func (node *FileNode) AddChild(name string, data FileInfo) (child *Fil...
    method Remove (line 109) | func (node *FileNode) Remove() error {
    method String (line 125) | func (node *FileNode) String() string {
    method MetadataString (line 139) | func (node *FileNode) MetadataString() string {
    method GetSize (line 184) | func (node *FileNode) GetSize() int64 {
    method VisitDepthChildFirst (line 210) | func (node *FileNode) VisitDepthChildFirst(visitor Visitor, evaluator ...
    method VisitDepthParentFirst (line 233) | func (node *FileNode) VisitDepthParentFirst(visitor Visitor, evaluator...
    method IsWhiteout (line 265) | func (node *FileNode) IsWhiteout() bool {
    method IsLeaf (line 270) | func (node *FileNode) IsLeaf() bool {
    method Path (line 275) | func (node *FileNode) Path() string {
    method deriveDiffType (line 300) | func (node *FileNode) deriveDiffType(diffType DiffType) error {
    method AssignDiffType (line 314) | func (node *FileNode) AssignDiffType(diffType DiffType) error {
    method compare (line 333) | func (node *FileNode) compare(other *FileNode) DiffType {
  function NewNode (line 37) | func NewNode(parent *FileNode, name string, data FileInfo) (node *FileNo...

FILE: dive/filetree/file_node_test.go
  function TestAddChild (line 7) | func TestAddChild(t *testing.T) {
  function TestRemoveChild (line 49) | func TestRemoveChild(t *testing.T) {
  function TestPath (line 85) | func TestPath(t *testing.T) {
  function TestIsWhiteout (line 96) | func TestIsWhiteout(t *testing.T) {
  function TestDiffTypeFromAddedChildren (line 115) | func TestDiffTypeFromAddedChildren(t *testing.T) {
  function TestDiffTypeFromRemovedChildren (line 133) | func TestDiffTypeFromRemovedChildren(t *testing.T) {
  function TestDirSize (line 154) | func TestDirSize(t *testing.T) {

FILE: dive/filetree/file_tree.go
  constant newLine (line 13) | newLine              = "\n"
  constant noBranchSpace (line 14) | noBranchSpace        = "    "
  constant branchSpace (line 15) | branchSpace          = "│   "
  constant middleItem (line 16) | middleItem           = "├─"
  constant lastItem (line 17) | lastItem             = "└─"
  constant whiteoutPrefix (line 18) | whiteoutPrefix       = ".wh."
  constant doubleWhiteoutPrefix (line 19) | doubleWhiteoutPrefix = ".wh..wh.."
  constant uncollapsedItem (line 20) | uncollapsedItem      = "─ "
  constant collapsedItem (line 21) | collapsedItem        = "⊕ "
  type FileTree (line 25) | type FileTree struct
    method renderStringTreeBetween (line 58) | func (tree *FileTree) renderStringTreeBetween(startRow, stopRow int, s...
    method VisibleSize (line 130) | func (tree *FileTree) VisibleSize() int {
    method String (line 159) | func (tree *FileTree) String(showAttributes bool) string {
    method StringBetween (line 164) | func (tree *FileTree) StringBetween(start, stop int, showAttributes bo...
    method Copy (line 169) | func (tree *FileTree) Copy() *FileTree {
    method VisitDepthChildFirst (line 196) | func (tree *FileTree) VisitDepthChildFirst(visitor Visitor, evaluator ...
    method VisitDepthParentFirst (line 202) | func (tree *FileTree) VisitDepthParentFirst(visitor Visitor, evaluator...
    method Stack (line 208) | func (tree *FileTree) Stack(upper *FileTree) (failed []PathError, stac...
    method GetNode (line 228) | func (tree *FileTree) GetNode(path string) (*FileNode, error) {
    method AddPath (line 244) | func (tree *FileTree) AddPath(filepath string, data FileInfo) (*FileNo...
    method RemovePath (line 285) | func (tree *FileTree) RemovePath(path string) error {
    method CompareAndMark (line 301) | func (tree *FileTree) CompareAndMark(upper *FileTree) ([]PathError, er...
    method markRemoved (line 368) | func (tree *FileTree) markRemoved(path string) error {
  function NewFileTree (line 35) | func NewFileTree() (tree *FileTree) {
  type renderParams (line 48) | type renderParams struct
  type Visitor (line 190) | type Visitor
  type VisitEvaluator (line 193) | type VisitEvaluator
  type compareMark (line 293) | type compareMark struct
  function StackTreeRange (line 377) | func StackTreeRange(trees []*FileTree, start, stop int) (*FileTree, []Pa...

FILE: dive/filetree/file_tree_test.go
  function stringInSlice (line 9) | func stringInSlice(a string, list []string) bool {
  function AssertDiffType (line 18) | func AssertDiffType(node *FileNode, expectedDiffType DiffType) error {
  function TestStringCollapsed (line 25) | func TestStringCollapsed(t *testing.T) {
  function TestString (line 64) | func TestString(t *testing.T) {
  function TestStringBetween (line 89) | func TestStringBetween(t *testing.T) {
  function TestRejectPurelyRelativePath (line 129) | func TestRejectPurelyRelativePath(t *testing.T) {
  function TestAddRelativePath (line 143) | func TestAddRelativePath(t *testing.T) {
  function TestAddPath (line 163) | func TestAddPath(t *testing.T) {
  function TestAddWhiteoutPath (line 210) | func TestAddWhiteoutPath(t *testing.T) {
  function TestRemovePath (line 234) | func TestRemovePath(t *testing.T) {
  function TestStack (line 287) | func TestStack(t *testing.T) {
  function TestCopy (line 382) | func TestCopy(t *testing.T) {
  function TestCompareWithNoChanges (line 437) | func TestCompareWithNoChanges(t *testing.T) {
  function TestCompareWithAdds (line 479) | func TestCompareWithAdds(t *testing.T) {
  function TestCompareWithChanges (line 549) | func TestCompareWithChanges(t *testing.T) {
  function TestCompareWithRemoves (line 662) | func TestCompareWithRemoves(t *testing.T) {
  function TestStackRange (line 733) | func TestStackRange(t *testing.T) {
  function TestRemoveOnIterate (line 805) | func TestRemoveOnIterate(t *testing.T) {

FILE: dive/filetree/node_data.go
  type NodeData (line 6) | type NodeData struct
    method Copy (line 22) | func (data *NodeData) Copy() *NodeData {
  function NewNodeData (line 13) | func NewNodeData() *NodeData {

FILE: dive/filetree/node_data_test.go
  function TestAssignDiffType (line 7) | func TestAssignDiffType(t *testing.T) {
  function TestMergeDiffTypes (line 19) | func TestMergeDiffTypes(t *testing.T) {
  function BlankFileChangeInfo (line 34) | func BlankFileChangeInfo(path string) (f *FileInfo) {

FILE: dive/filetree/order_strategy.go
  type SortOrder (line 7) | type SortOrder
  constant ByName (line 10) | ByName = iota
  constant BySizeDesc (line 11) | BySizeDesc
  constant NumSortOrderConventions (line 13) | NumSortOrderConventions
  type OrderStrategy (line 16) | type OrderStrategy interface
  function GetSortOrderStrategy (line 20) | func GetSortOrderStrategy(sortOrder SortOrder) OrderStrategy {
  type orderByNameStrategy (line 30) | type orderByNameStrategy struct
    method orderKeys (line 32) | func (orderByNameStrategy) orderKeys(files map[string]*FileNode) []str...
  type orderBySizeDescStrategy (line 43) | type orderBySizeDescStrategy struct
    method orderKeys (line 45) | func (orderBySizeDescStrategy) orderKeys(files map[string]*FileNode) [...

FILE: dive/filetree/path_error.go
  constant ActionAdd (line 6) | ActionAdd FileAction = iota
  constant ActionRemove (line 7) | ActionRemove
  type FileAction (line 10) | type FileAction
    method String (line 12) | func (fa FileAction) String() string {
  type PathError (line 23) | type PathError struct
    method String (line 37) | func (pe PathError) String() string {
  function NewPathError (line 29) | func NewPathError(path string, action FileAction, err error) PathError {

FILE: dive/filetree/view_info.go
  type ViewInfo (line 4) | type ViewInfo struct
    method Copy (line 18) | func (view *ViewInfo) Copy() (newView *ViewInfo) {
  function NewViewInfo (line 10) | func NewViewInfo() (view *ViewInfo) {

FILE: dive/get_image_resolver.go
  constant SourceUnknown (line 13) | SourceUnknown ImageSource = iota
  constant SourceDockerEngine (line 14) | SourceDockerEngine
  constant SourcePodmanEngine (line 15) | SourcePodmanEngine
  constant SourceDockerArchive (line 16) | SourceDockerArchive
  type ImageSource (line 19) | type ImageSource
    method String (line 23) | func (r ImageSource) String() string {
  function ParseImageSource (line 27) | func ParseImageSource(r string) ImageSource {
  function DeriveImageSource (line 42) | func DeriveImageSource(image string) (ImageSource, string) {
  function GetImageResolver (line 62) | func GetImageResolver(r ImageSource) (image.Resolver, error) {

FILE: dive/image/analysis.go
  type Analysis (line 8) | type Analysis struct
  function Analyze (line 20) | func Analyze(ctx context.Context, img *Image) (*Analysis, error) {

FILE: dive/image/docker/archive_resolver.go
  type archiveResolver (line 11) | type archiveResolver struct
    method Name (line 18) | func (r *archiveResolver) Name() string {
    method Fetch (line 22) | func (r *archiveResolver) Fetch(ctx context.Context, path string) (*im...
    method Build (line 36) | func (r *archiveResolver) Build(ctx context.Context, args []string) (*...
    method Extract (line 40) | func (r *archiveResolver) Extract(ctx context.Context, id string, l st...
  function NewResolverFromArchive (line 13) | func NewResolverFromArchive() *archiveResolver {

FILE: dive/image/docker/build.go
  constant defaultDockerfileName (line 13) | defaultDockerfileName    = "Dockerfile"
  constant defaultContainerfileName (line 14) | defaultContainerfileName = "Containerfile"
  function buildImageFromCli (line 17) | func buildImageFromCli(fs afero.Fs, buildArgs []string) (string, error) {
  function isFileFlagsAreSet (line 50) | func isFileFlagsAreSet(args []string, flags ...string) bool {
  function tryFindContainerfile (line 61) | func tryFindContainerfile(fs afero.Fs, buildArgs []string) (string, erro...

FILE: dive/image/docker/build_test.go
  function TestIsFileFlagsAreSet (line 12) | func TestIsFileFlagsAreSet(t *testing.T) {
  function TestTryFindContainerfile (line 83) | func TestTryFindContainerfile(t *testing.T) {
  function create (line 220) | func create(t testing.TB, fs afero.Fs, path, contents string) {

FILE: dive/image/docker/cli.go
  function runDockerCmd (line 13) | func runDockerCmd(cmdStr string, args ...string) error {
  function isDockerClientBinaryAvailable (line 34) | func isDockerClientBinaryAvailable() bool {

FILE: dive/image/docker/config.go
  type config (line 8) | type config struct
  type rootFs (line 13) | type rootFs struct
  type historyEntry (line 18) | type historyEntry struct
  function newConfig (line 27) | func newConfig(configBytes []byte) config {
  function isConfig (line 47) | func isConfig(configBytes []byte) bool {

FILE: dive/image/docker/docker_host_unix.go
  constant defaultDockerHost (line 6) | defaultDockerHost = "unix:///var/run/docker.sock"

FILE: dive/image/docker/docker_host_windows.go
  constant defaultDockerHost (line 4) | defaultDockerHost = "npipe:////.pipe/docker_engine"

FILE: dive/image/docker/engine_resolver.go
  type engineResolver (line 23) | type engineResolver struct
    method Name (line 30) | func (r *engineResolver) Name() string {
    method Fetch (line 34) | func (r *engineResolver) Fetch(ctx context.Context, id string) (*image...
    method Build (line 48) | func (r *engineResolver) Build(ctx context.Context, args []string) (*i...
    method Extract (line 56) | func (r *engineResolver) Extract(ctx context.Context, id string, l str...
    method fetchArchive (line 69) | func (r *engineResolver) fetchArchive(ctx context.Context, id string) ...
  function NewResolverFromEngine (line 25) | func NewResolverFromEngine() *engineResolver {
  function determineDockerHost (line 144) | func determineDockerHost() (string, error) {

FILE: dive/image/docker/image_archive.go
  type ImageArchive (line 21) | type ImageArchive struct
    method ToImage (line 250) | func (img *ImageArchive) ToImage(id string) (*image.Image, error) {
  function NewImageArchive (line 27) | func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) {
  function processLayerTar (line 200) | func processLayerTar(name string, reader *tar.Reader) (*filetree.FileTre...
  function getFileList (line 221) | func getFileList(tarReader *tar.Reader) ([]filetree.FileInfo, error) {
  function ExtractFromImage (line 303) | func ExtractFromImage(tarFile io.ReadCloser, l string, p string) error {
  function extractInner (line 337) | func extractInner(reader *tar.Reader, p string) error {

FILE: dive/image/docker/image_archive_analysis_test.go
  function Test_Analysis (line 7) | func Test_Analysis(t *testing.T) {

FILE: dive/image/docker/layer.go
  type layer (line 11) | type layer struct
    method ToLayer (line 18) | func (l *layer) ToLayer() *image.Layer {

FILE: dive/image/docker/manifest.go
  type manifest (line 8) | type manifest struct
  function newManifest (line 14) | func newManifest(manifestBytes []byte) manifest {

FILE: dive/image/docker/testing.go
  function TestLoadArchive (line 12) | func TestLoadArchive(t testing.TB, tarPath string) (*ImageArchive, error) {
  function TestAnalysisFromArchive (line 23) | func TestAnalysisFromArchive(t testing.TB, path string) *image.Analysis {

FILE: dive/image/image.go
  type Image (line 7) | type Image struct

FILE: dive/image/layer.go
  constant LayerFormat (line 13) | LayerFormat = "%7s  %s"
  type Layer (line 16) | type Layer struct
    method ShortId (line 26) | func (l *Layer) ShortId() string {
    method commandPreview (line 37) | func (l *Layer) commandPreview() string {
    method String (line 43) | func (l *Layer) String() string {

FILE: dive/image/podman/build.go
  function buildImageFromCli (line 9) | func buildImageFromCli(buildArgs []string) (string, error) {

FILE: dive/image/podman/cli.go
  function runPodmanCmd (line 16) | func runPodmanCmd(cmdStr string, args ...string) error {
  function streamPodmanCmd (line 36) | func streamPodmanCmd(args ...string) (error, io.Reader) {
  function isPodmanClientBinaryAvailable (line 60) | func isPodmanClientBinaryAvailable() bool {

FILE: dive/image/podman/resolver.go
  type resolver (line 14) | type resolver struct
    method Name (line 21) | func (r *resolver) Name() string {
    method Build (line 25) | func (r *resolver) Build(ctx context.Context, args []string) (*image.I...
    method Fetch (line 33) | func (r *resolver) Fetch(ctx context.Context, id string) (*image.Image...
    method Extract (line 44) | func (r *resolver) Extract(ctx context.Context, id string, l string, p...
    method resolveFromDockerArchive (line 59) | func (r *resolver) resolveFromDockerArchive(id string) (*image.Image, ...
  function NewResolverFromEngine (line 16) | func NewResolverFromEngine() *resolver {

FILE: dive/image/podman/resolver_unsupported.go
  type resolver (line 13) | type resolver struct
    method Name (line 20) | func (r *resolver) Name() string {
    method Build (line 23) | func (r *resolver) Build(ctx context.Context, args []string) (*image.I...
    method Fetch (line 27) | func (r *resolver) Fetch(ctx context.Context, id string) (*image.Image...
    method Extract (line 31) | func (r *resolver) Extract(ctx context.Context, id string, l string, p...
  function NewResolverFromEngine (line 15) | func NewResolverFromEngine() *resolver {

FILE: dive/image/resolver.go
  type Resolver (line 5) | type Resolver interface
  type ContentReader (line 12) | type ContentReader interface

FILE: internal/bus/bus.go
  function Set (line 7) | func Set(p partybus.Publisher) {
  function Get (line 11) | func Get() partybus.Publisher {
  function Publish (line 15) | func Publish(e partybus.Event) {

FILE: internal/bus/event/event.go
  constant typePrefix (line 8) | typePrefix = "dive-cli"
  constant TaskStarted (line 11) | TaskStarted partybus.EventType = typePrefix + "-task-started"
  constant ExploreAnalysis (line 14) | ExploreAnalysis partybus.EventType = typePrefix + "-analysis"
  constant Report (line 17) | Report partybus.EventType = typePrefix + "-report"
  constant Notification (line 20) | Notification partybus.EventType = typePrefix + "-notification"

FILE: internal/bus/event/parser/parsers.go
  type ErrBadPayload (line 12) | type ErrBadPayload struct
    method Error (line 18) | func (e *ErrBadPayload) Error() string {
  function newPayloadErr (line 22) | func newPayloadErr(t partybus.EventType, field string, value interface{}...
  function checkEventType (line 30) | func checkEventType(actual, expected partybus.EventType) error {
  function ParseTaskStarted (line 37) | func ParseTaskStarted(e partybus.Event) (progress.StagedProgressable, *p...
  function ParseExploreAnalysis (line 57) | func ParseExploreAnalysis(e partybus.Event) (image.Analysis, image.Conte...
  function ParseReport (line 70) | func ParseReport(e partybus.Event) (string, string, error) {
  function ParseNotification (line 89) | func ParseNotification(e partybus.Event) (string, string, error) {

FILE: internal/bus/event/payload/explore.go
  type Explore (line 5) | type Explore struct

FILE: internal/bus/event/payload/generic.go
  type genericProgressKey (line 8) | type genericProgressKey struct
  function SetGenericProgressToContext (line 10) | func SetGenericProgressToContext(ctx context.Context, mon *GenericProgre...
  function GetGenericProgressFromContext (line 14) | func GetGenericProgressFromContext(ctx context.Context) *GenericProgress {
  type GenericTask (line 22) | type GenericTask struct
  type GenericProgress (line 39) | type GenericProgress struct
  type Title (line 44) | type Title struct

FILE: internal/bus/helpers.go
  function Report (line 11) | func Report(report string) {
  function Notify (line 21) | func Notify(message string) {
  function StartTask (line 28) | func StartTask(info payload.GenericTask) *payload.GenericProgress {
  function StartSizedTask (line 43) | func StartSizedTask(info payload.GenericTask, size int64, initialStage s...
  function ExploreAnalysis (line 58) | func ExploreAnalysis(analysis image.Analysis, reader image.ContentReader) {

FILE: internal/log/log.go
  function Set (line 12) | func Set(l logger.Logger) {
  function Get (line 17) | func Get() logger.Logger {
  function Errorf (line 22) | func Errorf(format string, args ...interface{}) {
  function Error (line 27) | func Error(args ...interface{}) {
  function Warnf (line 32) | func Warnf(format string, args ...interface{}) {
  function Warn (line 37) | func Warn(args ...interface{}) {
  function Infof (line 42) | func Infof(format string, args ...interface{}) {
  function Info (line 47) | func Info(args ...interface{}) {
  function Debugf (line 52) | func Debugf(format string, args ...interface{}) {
  function Debug (line 57) | func Debug(args ...interface{}) {
  function Tracef (line 62) | func Tracef(format string, args ...interface{}) {
  function Trace (line 67) | func Trace(args ...interface{}) {
  function WithFields (line 72) | func WithFields(fields ...interface{}) logger.MessageLogger {
  function Nested (line 77) | func Nested(fields ...interface{}) logger.Logger {

FILE: internal/utils/format.go
  function CleanArgs (line 8) | func CleanArgs(s []string) []string {

FILE: internal/utils/view.go
  function IsNewView (line 10) | func IsNewView(errs ...error) bool {
Condensed preview — 170 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (678K chars).
[
  {
    "path": ".binny.yaml",
    "chars": 1321,
    "preview": "tools:\n  # we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)\n  - name: binny\n "
  },
  {
    "path": ".bouncer.yaml",
    "chars": 235,
    "preview": "permit:\n  - BSD.*\n  - MIT.*\n  - Apache.*\n  - MPL.*\n  - ISC\n  - WTFPL\n  - Unlicense\n\nignore-packages:\n  # crypto/internal"
  },
  {
    "path": ".data/.dive-ci",
    "chars": 519,
    "preview": "---\nplugins:\n  - plugin1\n\nrules:\n  # If the efficiency is measured below X%, mark as failed. (expressed as a percentage "
  },
  {
    "path": ".data/Dockerfile.example",
    "chars": 568,
    "preview": "FROM busybox:latest\nADD README.md /somefile.txt\nRUN mkdir -p /root/example/really/nested\nRUN cp /somefile.txt /root/exam"
  },
  {
    "path": ".data/Dockerfile.minimal",
    "chars": 39,
    "preview": "FROM scratch\nCOPY README.md /README.md\n"
  },
  {
    "path": ".data/Dockerfile.test-image",
    "chars": 548,
    "preview": "FROM busybox:latest\nADD README.md /somefile.txt\nRUN mkdir -p /root/example/really/nested\nRUN cp /somefile.txt /root/exam"
  },
  {
    "path": ".dockerignore",
    "chars": 103,
    "preview": "/.git\n/.data\n/.cover\n/dist\n!/dist/dive_linux_amd64\n/ui\n/internal/utils\n/image\n/cmd\n/build\ncoverage.txt\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 22,
    "preview": "github: ['wagoodman']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 327,
    "preview": "---\nname: Bug report\nabout: Something isn't working as expected\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**What happene"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 292,
    "preview": "---\nname: Feature request\nabout: Got an idea for a new feature? Let us know!\ntitle: ''\nlabels: enhancement\nassignees: ''"
  },
  {
    "path": ".github/actions/bootstrap/action.yaml",
    "chars": 1297,
    "preview": "name: \"Bootstrap\"\ndescription: \"Bootstrap all tools and dependencies\"\ninputs:\n  go-version:\n    description: \"Go version"
  },
  {
    "path": ".github/dependabot.yaml",
    "chars": 297,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package"
  },
  {
    "path": ".github/scripts/coverage.py",
    "chars": 861,
    "preview": "#!/usr/bin/env python3\nimport subprocess\nimport sys\nimport shlex\n\n\nclass bcolors:\n    HEADER = '\\033[95m'\n    OKBLUE = '"
  },
  {
    "path": ".github/scripts/trigger-release.sh",
    "chars": 1636,
    "preview": "#!/usr/bin/env bash\nset -eu\n\nbold=$(tput bold)\nnormal=$(tput sgr0)\n\nif ! [ -x \"$(command -v gh)\" ]; then\n    echo \"The G"
  },
  {
    "path": ".github/workflows/release.yaml",
    "chars": 5138,
    "preview": "name: \"Release\"\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: tag the latest commit on main w"
  },
  {
    "path": ".github/workflows/validations.yaml",
    "chars": 3584,
    "preview": "name: \"Validations\"\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  Static-Analysis"
  },
  {
    "path": ".gitignore",
    "chars": 458,
    "preview": "# app configs\n.dive.yaml\n\n# misc\n/.image\n*.log\nCHANGELOG.md\nVERSION\n\n# IDEs\n/.idea\n/.vscode\n\n# tooling\n/bin\n/.tool-versi"
  },
  {
    "path": ".golangci.yaml",
    "chars": 2578,
    "preview": "# TODO: enable this when we have coverage on docstring comments\n#issues:\n#  # The list of ids of default excludes to inc"
  },
  {
    "path": ".goreleaser.yaml",
    "chars": 4616,
    "preview": "version: 2\n\nrelease:\n  # If set to auto, will mark the release as not ready for production in case there is an indicator"
  },
  {
    "path": "Dockerfile",
    "chars": 471,
    "preview": "FROM alpine:3.21 AS base\n\nARG DOCKER_CLI_VERSION=${DOCKER_CLI_VERSION}\nRUN wget -O- https://download.docker.com/linux/st"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2018 Alex Goodman\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "Makefile",
    "chars": 1411,
    "preview": "OWNER = wagoodman\nPROJECT = dive\n\nTOOL_DIR = .tool\nBINNY = $(TOOL_DIR)/binny\nTASK = $(TOOL_DIR)/task\n\n.DEFAULT_GOAL := m"
  },
  {
    "path": "README.md",
    "chars": 11701,
    "preview": "# dive\n[![GitHub release](https://img.shields.io/github/release/wagoodman/dive.svg)](https://github.com/wagoodman/dive/r"
  },
  {
    "path": "RELEASE.md",
    "chars": 1045,
    "preview": "# Release process\n\n\n## Creating a release\n\n**Trigger a new release with `make release`**. \n\nAt this point you'll see a p"
  },
  {
    "path": "Taskfile.yaml",
    "chars": 11467,
    "preview": "\nversion: \"3\"\nvars:\n  OWNER: wagoodman\n  PROJECT: dive\n\n  # static file dirs\n  TOOL_DIR: .tool\n  TMP_DIR: .tmp\n\n  # TOOL"
  },
  {
    "path": "cmd/dive/cli/cli.go",
    "chars": 1365,
    "preview": "package cli\n\nimport (\n\t\"github.com/anchore/clio\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/inte"
  },
  {
    "path": "cmd/dive/cli/cli_build_test.go",
    "chars": 3188,
    "preview": "package cli\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"regexp\"\n\t\"testing\"\n"
  },
  {
    "path": "cmd/dive/cli/cli_ci_test.go",
    "chars": 1910,
    "preview": "package cli\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"testing\"\n)\n\nfunc Te"
  },
  {
    "path": "cmd/dive/cli/cli_config_test.go",
    "chars": 355,
    "preview": "package cli\n\nimport (\n\t\"github.com/stretchr/testify/require\"\n\t\"testing\"\n)\n\nfunc Test_Config(t *testing.T) {\n\tt.Setenv(\"D"
  },
  {
    "path": "cmd/dive/cli/cli_json_test.go",
    "chars": 724,
    "preview": "package cli\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"os\"\n\t\"path/filepath"
  },
  {
    "path": "cmd/dive/cli/cli_load_test.go",
    "chars": 3050,
    "preview": "package cli\n\nimport (\n\t\"fmt\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"os\"\n\t\"os/exe"
  },
  {
    "path": "cmd/dive/cli/cli_test.go",
    "chars": 3152,
    "preview": "package cli\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"github.com/anchore/clio\"\n\t\"github.com/charmbracelet/lipgloss\"\n\tsnapsPkg \"github"
  },
  {
    "path": "cmd/dive/cli/internal/command/adapter/analyzer.go",
    "chars": 1695,
    "preview": "package adapter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/dustin/go-humanize\"\n\t\"github.com/wagoodman/dive/dive/image\"\n\t\"g"
  },
  {
    "path": "cmd/dive/cli/internal/command/adapter/evaluator.go",
    "chars": 1272,
    "preview": "package adapter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/command/ci\"\n\t\"github.com/w"
  },
  {
    "path": "cmd/dive/cli/internal/command/adapter/exporter.go",
    "chars": 1540,
    "preview": "package adapter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/"
  },
  {
    "path": "cmd/dive/cli/internal/command/adapter/resolver.go",
    "chars": 2245,
    "preview": "package adapter\n\nimport (\n\t\"context\"\n\t\"github.com/wagoodman/dive/dive/image\"\n\t\"github.com/wagoodman/dive/internal/bus\"\n\t"
  },
  {
    "path": "cmd/dive/cli/internal/command/build.go",
    "chars": 1363,
    "preview": "package command\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/clio\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/wagoodman/dive/cmd/di"
  },
  {
    "path": "cmd/dive/cli/internal/command/ci/evaluator.go",
    "chars": 7417,
    "preview": "package ci\n\nimport (\n\t\"fmt\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"golang.org/x/net/context\"\n\t\"sort\"\n\t\"strconv\"\n\t\"string"
  },
  {
    "path": "cmd/dive/cli/internal/command/ci/evaluator_test.go",
    "chars": 4965,
    "preview": "package ci\n\nimport (\n\t\"context\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/atomic\"\n\t\"os/exec\"\n\t\"path/filepath\""
  },
  {
    "path": "cmd/dive/cli/internal/command/ci/rule.go",
    "chars": 831,
    "preview": "package ci\n\nimport (\n\t\"github.com/wagoodman/dive/dive/image\"\n)\n\nconst (\n\tRuleUnknown = iota\n\tRulePassed\n\tRuleFailed\n\tRul"
  },
  {
    "path": "cmd/dive/cli/internal/command/ci/rules.go",
    "chars": 5305,
    "preview": "package ci\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/dustin/go-humanize\"\n\t\"github.com/wagoodman/dive/dive/image\"\n\t\"strconv"
  },
  {
    "path": "cmd/dive/cli/internal/command/export/export.go",
    "chars": 2426,
    "preview": "package export\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/wagoodman/dive/dive/filetree\"\n\tdiveImage \"github.com/wagoodman/di"
  },
  {
    "path": "cmd/dive/cli/internal/command/export/export_test.go",
    "chars": 387,
    "preview": "package export\n\nimport (\n\t\"testing\"\n\n\t\"github.com/wagoodman/dive/dive/image/docker\"\n)\n\nfunc Test_Export(t *testing.T) {\n"
  },
  {
    "path": "cmd/dive/cli/internal/command/export/main_test.go",
    "chars": 1387,
    "preview": "package export\n\nimport (\n\t\"flag\"\n\t\"github.com/charmbracelet/lipgloss\"\n\tsnapsPkg \"github.com/gkampitakis/go-snaps/snaps\"\n"
  },
  {
    "path": "cmd/dive/cli/internal/command/export/testdata/snapshots/export_test.snap",
    "chars": 83651,
    "preview": "\n[Test_Export - 1]\n{\n \"image\": {\n  \"efficiencyScore\": 0.9844212134184309,\n  \"fileReference\": [\n   {\n    \"count\": 2,\n    "
  },
  {
    "path": "cmd/dive/cli/internal/command/root.go",
    "chars": 2777,
    "preview": "package command\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/anchore/clio\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/"
  },
  {
    "path": "cmd/dive/cli/internal/options/analysis.go",
    "chars": 2549,
    "preview": "package options\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/clio\"\n\t\"github.com/scylladb/go-set/strset\"\n\t\"github.com/wagoodman/"
  },
  {
    "path": "cmd/dive/cli/internal/options/application.go",
    "chars": 936,
    "preview": "package options\n\nimport (\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1\"\n)\n\ntype Application struct {\n\tAnalysi"
  },
  {
    "path": "cmd/dive/cli/internal/options/ci.go",
    "chars": 2965,
    "preview": "package options\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/clio\"\n\t\"gopkg.in/yaml.v3\"\n\t\"os\"\n)\n\nvar _ interface {\n\tclio.PostLoa"
  },
  {
    "path": "cmd/dive/cli/internal/options/ci_rules.go",
    "chars": 3180,
    "preview": "package options\n\nimport (\n\t\"github.com/anchore/clio\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/command/ci\"\n\t\"git"
  },
  {
    "path": "cmd/dive/cli/internal/options/export.go",
    "chars": 850,
    "preview": "package options\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\n\t\"github.com/anchore/clio\"\n)\n\nvar _ interface {\n\tclio.FlagAdder\n\tclio.Po"
  },
  {
    "path": "cmd/dive/cli/internal/options/ui.go",
    "chars": 519,
    "preview": "package options\n\n// UI combines all UI configuration elements\ntype UI struct {\n\tKeybinding UIKeybindings `yaml:\"keybindi"
  },
  {
    "path": "cmd/dive/cli/internal/options/ui_diff.go",
    "chars": 1012,
    "preview": "package options\n\nimport (\n\t\"github.com/anchore/clio\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1\"\n\t\"github.c"
  },
  {
    "path": "cmd/dive/cli/internal/options/ui_filetree.go",
    "chars": 1404,
    "preview": "package options\n\nimport (\n\t\"github.com/anchore/clio\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1\"\n\t\"github.c"
  },
  {
    "path": "cmd/dive/cli/internal/options/ui_keybindings.go",
    "chars": 6491,
    "preview": "package options\n\nimport (\n\t\"github.com/anchore/clio\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/key\"\n\t\"refl"
  },
  {
    "path": "cmd/dive/cli/internal/options/ui_layers.go",
    "chars": 550,
    "preview": "package options\n\nimport \"github.com/anchore/clio\"\n\nvar _ clio.FieldDescriber = (*UILayers)(nil)\n\n// UILayers provides co"
  },
  {
    "path": "cmd/dive/cli/internal/ui/no_ui.go",
    "chars": 446,
    "preview": "package ui\n\nimport (\n\t\"github.com/wagoodman/go-partybus\"\n\n\t\"github.com/anchore/clio\"\n)\n\nvar _ clio.UI = (*NoUI)(nil)\n\nty"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/app/app.go",
    "chars": 3330,
    "preview": "package app\n\nimport (\n\t\"errors\"\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/app/controller.go",
    "chars": 6352,
    "preview": "package app\n\nimport (\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1\"\n\t\"github.com/wagoodman/dive/cmd/di"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/app/job_control_other.go",
    "chars": 209,
    "preview": "//go:build windows\n// +build windows\n\npackage app\n\nimport (\n\t\"github.com/awesome-gocui/gocui\"\n)\n\n// handle ctrl+z not su"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/app/job_control_unix.go",
    "chars": 315,
    "preview": "//go:build !windows\n// +build !windows\n\npackage app\n\nimport (\n\t\"syscall\"\n\n\t\"github.com/awesome-gocui/gocui\"\n)\n\n// handle"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/config.go",
    "chars": 1670,
    "preview": "package v1\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/key\"\n\t\"github.com/wagoodma"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/format/format.go",
    "chars": 3445,
    "preview": "package format\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/lunixbochs/vtclean\"\n)\n\nconst (\n\t// se"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/key/binding.go",
    "chars": 2037,
    "preview": "package key\n\nimport (\n\t\"fmt\"\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/awesome-gocui/keybinding\"\n\t\"github.com/wagoo"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/key/config.go",
    "chars": 3750,
    "preview": "package key\n\nimport (\n\t\"fmt\"\n\t\"github.com/awesome-gocui/keybinding\"\n)\n\ntype Config struct {\n\tInput string\n\tKeys  []keybi"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/layout/area.go",
    "chars": 65,
    "preview": "package layout\n\ntype Area struct {\n\tminX, minY, maxX, maxY int\n}\n"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/layout/compound/layer_details_column.go",
    "chars": 3625,
    "preview": "package compound\n\nimport (\n\t\"fmt\"\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/layout/layout.go",
    "chars": 231,
    "preview": "package layout\n\nimport \"github.com/awesome-gocui/gocui\"\n\ntype Layout interface {\n\tName() string\n\tLayout(g *gocui.Gui, mi"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/layout/location.go",
    "chars": 109,
    "preview": "package layout\n\nconst (\n\tLocationFooter Location = iota\n\tLocationHeader\n\tLocationColumn\n)\n\ntype Location int\n"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/layout/manager.go",
    "chars": 7957,
    "preview": "package layout\n\nimport (\n\t\"fmt\"\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/wagoodman/dive/internal/log\"\n)\n\ntype Cons"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/layout/manager_test.go",
    "chars": 6964,
    "preview": "package layout\n\nimport (\n\t\"testing\"\n\n\t\"github.com/awesome-gocui/gocui\"\n)\n\ntype testElement struct {\n\tt          *testing"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/cursor.go",
    "chars": 959,
    "preview": "package view\n\nimport (\n\t\"errors\"\n\n\t\"github.com/awesome-gocui/gocui\"\n)\n\n// CursorDown moves the cursor down in the curren"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/debug.go",
    "chars": 3023,
    "preview": "package view\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/wagoodman/d"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/filetree.go",
    "chars": 13207,
    "preview": "package view\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1\"\n\t"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/filter.go",
    "chars": 4884,
    "preview": "package view\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/fo"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/image_details.go",
    "chars": 4750,
    "preview": "package view\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/fo"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/layer.go",
    "chars": 9711,
    "preview": "package view\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/wagoodman/d"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/layer_change_listener.go",
    "chars": 155,
    "preview": "package view\n\nimport (\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/viewmodel\"\n)\n\ntype LayerChangeListener fu"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/layer_details.go",
    "chars": 3601,
    "preview": "package view\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/fo"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/renderer.go",
    "chars": 198,
    "preview": "package view\n\n// Controller defines the a renderable terminal screen pane.\ntype Renderer interface {\n\tUpdate() error\n\tRe"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/status.go",
    "chars": 3163,
    "preview": "package view\n\nimport (\n\t\"fmt\"\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1/fo"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/view/views.go",
    "chars": 1395,
    "preview": "package view\n\nimport (\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1\"\n)\n\ntyp"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/config.go",
    "chars": 18,
    "preview": "package viewmodel\n"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/filetree.go",
    "chars": 13682,
    "preview": "package viewmodel\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1\"\n\t\"github.com/wagood"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/filetree_test.go",
    "chars": 9796,
    "preview": "package viewmodel\n\nimport (\n\t\"flag\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/s"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/layer_compare.go",
    "chars": 118,
    "preview": "package viewmodel\n\nconst (\n\tCompareSingleLayer LayerCompareMode = iota\n\tCompareAllLayers\n)\n\ntype LayerCompareMode int\n"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/layer_selection.go",
    "chars": 239,
    "preview": "package viewmodel\n\nimport (\n\t\"github.com/wagoodman/dive/dive/image\"\n)\n\ntype LayerSelection struct {\n\tLayer              "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/layer_set_state.go",
    "chars": 1106,
    "preview": "package viewmodel\n\nimport \"github.com/wagoodman/dive/dive/image\"\n\ntype LayerSetState struct {\n\tLayerIndex        int\n\tLa"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/layer_set_state_test.go",
    "chars": 1339,
    "preview": "package viewmodel\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGetCompareIndexes(t *testing.T) {\n\ttests := []struct {\n\t\tname        "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileShowAggregateChanges.txt",
    "chars": 1816,
    "preview": "drwxr-xr-x         0:0     1.2 MB  ├─⊕ bin\ndrwxr-xr-x         0:0        0 B  ├── dev\ndrwxr-xr-x         0:0     1.0 kB "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeDirCollapse.txt",
    "chars": 543,
    "preview": "drwxr-xr-x         0:0     1.2 MB  ├─⊕ bin\ndrwxr-xr-x         0:0        0 B  ├── dev\ndrwxr-xr-x         0:0     1.0 kB "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeDirCollapseAll.txt",
    "chars": 347,
    "preview": "drwxr-xr-x         0:0     1.2 MB  ├─⊕ bin\ndrwxr-xr-x         0:0        0 B  ├── dev\ndrwxr-xr-x         0:0     1.0 kB "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeDirCursorRight.txt",
    "chars": 1029,
    "preview": "drwxr-xr-x         0:0     1.2 MB  ├─⊕ bin\ndrwxr-xr-x         0:0        0 B  ├── dev\ndrwxr-xr-x         0:0     1.0 kB "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeFilterTree.txt",
    "chars": 328,
    "preview": "drwxr-xr-x         0:0        0 B  └── etc\ndrwxr-xr-x         0:0        0 B      └── network\ndrwxr-xr-x         0:0    "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeGoCase.txt",
    "chars": 23820,
    "preview": "drwxr-xr-x         0:0     1.2 MB  ├── bin\n-rwxr-xr-x         0:0     1.1 MB  │   ├── [\n-rwxr-xr-x         0:0        0 "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeHideAddedRemovedModified.txt",
    "chars": 985,
    "preview": "drwxr-xr-x         0:0     1.2 MB  ├─⊕ bin\ndrwxr-xr-x         0:0        0 B  ├── dev\ndrwxr-xr-x         0:0     1.0 kB "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeHideTypeWithFilter.txt",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeHideUnmodified.txt",
    "chars": 496,
    "preview": "drwx------         0:0      19 kB  ├── root\ndrwxr-xr-x         0:0      13 kB  │   ├── example\ndrwxr-xr-x         0:0   "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeNoAttributes.txt",
    "chars": 9295,
    "preview": "├── bin\n│   ├── [\n│   ├── [[ → bin/[\n│   ├── acpid → bin/[\n│   ├── add-shell → bin/[\n│   ├── addgroup → bin/[\n│   ├── ad"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreePageDown.txt",
    "chars": 572,
    "preview": "-rwxr-xr-x         0:0        0 B  │   ├── cat → bin/[\n-rwxr-xr-x         0:0        0 B  │   ├── chat → bin/[\n-rwxr-xr-"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreePageUp.txt",
    "chars": 573,
    "preview": "-rwxr-xr-x         0:0        0 B  │   ├── arch → bin/[\n-rwxr-xr-x         0:0        0 B  │   ├── arp → bin/[\n-rwxr-xr-"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeRestrictedHeight.txt",
    "chars": 453,
    "preview": "├── bin\n│   ├── [\n│   ├── [[ → bin/[\n│   ├── acpid → bin/[\n│   ├── add-shell → bin/[\n│   ├── addgroup → bin/[\n│   ├── ad"
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1/viewmodel/testdata/TestFileTreeSelectLayer.txt",
    "chars": 1081,
    "preview": "drwxr-xr-x         0:0     1.2 MB  ├─⊕ bin\ndrwxr-xr-x         0:0        0 B  ├── dev\ndrwxr-xr-x         0:0     1.0 kB "
  },
  {
    "path": "cmd/dive/cli/internal/ui/v1.go",
    "chars": 4164,
    "preview": "package ui\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/anchore/clio\"\n\t\"github.com/anchore/go-logger/adapter/discard\"\n\t\"gith"
  },
  {
    "path": "cmd/dive/cli/testdata/config/dive-ci-legacy.yaml",
    "chars": 92,
    "preview": "rules:\n  lowestEfficiency: 0.95\n  highestWastedBytes: 20MB\n  highestUserWastedPercent: 0.20\n"
  },
  {
    "path": "cmd/dive/cli/testdata/default-ci-config/.dive-ci",
    "chars": 469,
    "preview": "rules:\n  # If the efficiency is measured below X%, mark as failed. (expressed as a percentage between 0-1)\n  lowestEffic"
  },
  {
    "path": "cmd/dive/cli/testdata/dive-enable-ci.yaml",
    "chars": 536,
    "preview": "ci: true\nrules:\n  # lowest allowable image efficiency (as a ratio between 0-1), otherwise CI validation will fail. (env:"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-containerfile/Containerfile",
    "chars": 550,
    "preview": "FROM busybox:1.37.0@sha256:37f7b378a29ceb4c551b1b5582e27747b855bbfaa73fa11914fe0df028dc581f\nADD example.md /somefile.txt"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-containerfile/dive-pass.yaml",
    "chars": 536,
    "preview": "ci: true\nrules:\n  # lowest allowable image efficiency (as a ratio between 0-1), otherwise CI validation will fail. (env:"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-containerfile/example.md",
    "chars": 17,
    "preview": "# exmaple!\n\nwoot!"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-containerfile/overwrite.md",
    "chars": 46,
    "preview": "# evil!\n\nthis will overwrite the other file..."
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-dockerfile/Dockerfile",
    "chars": 550,
    "preview": "FROM busybox:1.37.0@sha256:37f7b378a29ceb4c551b1b5582e27747b855bbfaa73fa11914fe0df028dc581f\nADD example.md /somefile.txt"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-dockerfile/dive-fail.yaml",
    "chars": 53,
    "preview": "ci: true\nrules:\n  lowest-efficiency-threshold: '0.9'\n"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-dockerfile/dive-pass.yaml",
    "chars": 123,
    "preview": "ci: true\nrules:\n  lowest-efficiency-threshold: '0.10'\n  highest-wasted-bytes: '20MB'\n  highest-user-wasted-percent: '0.9"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-dockerfile/example.md",
    "chars": 17,
    "preview": "# exmaple!\n\nwoot!"
  },
  {
    "path": "cmd/dive/cli/testdata/image-multi-layer-dockerfile/overwrite.md",
    "chars": 46,
    "preview": "# evil!\n\nthis will overwrite the other file..."
  },
  {
    "path": "cmd/dive/cli/testdata/invalid/Dockerfile",
    "chars": 26,
    "preview": "FROM scratch\nINVALID woops"
  },
  {
    "path": "cmd/dive/cli/testdata/snapshots/cli_build_test.snap",
    "chars": 3446,
    "preview": "\n[Test_Build_Dockerfile/implicit_dockerfile - 1]\nAnalysis:\n  efficiency:        100.00 %\n  wastedBytes:       131 bytes "
  },
  {
    "path": "cmd/dive/cli/testdata/snapshots/cli_ci_test.snap",
    "chars": 2024,
    "preview": "\n[Test_CI_Fail - 1]\nAnalysis:\n  efficiency:        100.00 %\n  wastedBytes:       131 bytes (131 B)\n  userWastedPercent: "
  },
  {
    "path": "cmd/dive/cli/testdata/snapshots/cli_config_test.snap",
    "chars": 4281,
    "preview": "\n[Test_Config - 1]\nlog:\n  # suppress all logging output (env: DIVE_LOG_QUIET)\n  quiet: false\n\n  # explicitly set the log"
  },
  {
    "path": "cmd/dive/cli/testdata/snapshots/cli_json_test.snap",
    "chars": 77618,
    "preview": "\n[Test_JsonOutput/json_output - 1]\n{\n \"image\": {\n  \"efficiencyScore\": 1,\n  \"fileReference\": [],\n  \"inefficientBytes\": 0,"
  },
  {
    "path": "cmd/dive/cli/testdata/snapshots/cli_load_test.snap",
    "chars": 3675,
    "preview": "\n[Test_LoadImage/from_docker_engine - 1]\nLoading image                 busybox:1.37.0@sha256:ad9fa4d07136a83e69a54ef0010"
  },
  {
    "path": "cmd/dive/main.go",
    "chars": 1876,
    "preview": "package main\n\n// Copyright © 2018 Alex Goodman\n//\n// Permission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "dive/filetree/comparer.go",
    "chars": 4727,
    "preview": "package filetree\n\nimport (\n\t\"fmt\"\n)\n\ntype TreeIndexKey struct {\n\tbottomTreeStart, bottomTreeStop, topTreeStart, topTreeS"
  },
  {
    "path": "dive/filetree/diff.go",
    "chars": 744,
    "preview": "package filetree\n\nimport (\n\t\"fmt\"\n)\n\nconst (\n\tUnmodified DiffType = iota\n\tModified\n\tAdded\n\tRemoved\n)\n\n// DiffType define"
  },
  {
    "path": "dive/filetree/efficiency.go",
    "chars": 3794,
    "preview": "package filetree\n\nimport (\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/internal/log\"\n\t\"sort\"\n)\n\n// EfficiencyData represents the "
  },
  {
    "path": "dive/filetree/efficiency_test.go",
    "chars": 2404,
    "preview": "package filetree\n\nimport (\n\t\"testing\"\n)\n\nfunc checkError(t *testing.T, err error, message string) {\n\tif err != nil {\n\t\tt"
  },
  {
    "path": "dive/filetree/file_info.go",
    "chars": 3164,
    "preview": "package filetree\n\nimport (\n\t\"archive/tar\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/cespare/xxhash/v2\"\n)\n\n// FileInfo contains ta"
  },
  {
    "path": "dive/filetree/file_node.go",
    "chars": 9514,
    "preview": "package filetree\n\nimport (\n\t\"archive/tar\"\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/internal/log\"\n\t\"strings\"\n\n\t\"github.com/dust"
  },
  {
    "path": "dive/filetree/file_node_test.go",
    "chars": 4729,
    "preview": "package filetree\n\nimport (\n\t\"testing\"\n)\n\nfunc TestAddChild(t *testing.T) {\n\tvar expected, actual int\n\ttree := NewFileTre"
  },
  {
    "path": "dive/filetree/file_tree.go",
    "chars": 11962,
    "preview": "package filetree\n\nimport (\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/internal/log\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/google/uuid"
  },
  {
    "path": "dive/filetree/file_tree_test.go",
    "chars": 21200,
    "preview": "package filetree\n\nimport (\n\t\"fmt\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n)\n\nfunc stringInSlice(a string, list "
  },
  {
    "path": "dive/filetree/node_data.go",
    "chars": 581,
    "preview": "package filetree\n\nvar GlobalFileTreeCollapse bool\n\n// NodeData is the payload for a FileNode\ntype NodeData struct {\n\tVie"
  },
  {
    "path": "dive/filetree/node_data_test.go",
    "chars": 829,
    "preview": "package filetree\n\nimport (\n\t\"testing\"\n)\n\nfunc TestAssignDiffType(t *testing.T) {\n\ttree := NewFileTree()\n\tnode, _, err :="
  },
  {
    "path": "dive/filetree/order_strategy.go",
    "chars": 1057,
    "preview": "package filetree\n\nimport (\n\t\"sort\"\n)\n\ntype SortOrder int\n\nconst (\n\tByName = iota\n\tBySizeDesc\n\n\tNumSortOrderConventions\n)"
  },
  {
    "path": "dive/filetree/path_error.go",
    "chars": 623,
    "preview": "package filetree\n\nimport \"fmt\"\n\nconst (\n\tActionAdd FileAction = iota\n\tActionRemove\n)\n\ntype FileAction int\n\nfunc (fa File"
  },
  {
    "path": "dive/filetree/view_info.go",
    "chars": 444,
    "preview": "package filetree\n\n// ViewInfo contains UI specific detail for a specific FileNode\ntype ViewInfo struct {\n\tCollapsed bool"
  },
  {
    "path": "dive/get_image_resolver.go",
    "chars": 1756,
    "preview": "package dive\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/wagoodman/dive/dive/image\"\n\t\"github.com/wagoodman/dive/dive/image"
  },
  {
    "path": "dive/image/analysis.go",
    "chars": 1209,
    "preview": "package image\n\nimport (\n\t\"context\"\n\t\"github.com/wagoodman/dive/dive/filetree\"\n)\n\ntype Analysis struct {\n\tImage          "
  },
  {
    "path": "dive/image/docker/archive_resolver.go",
    "chars": 934,
    "preview": "package docker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/wagoodman/dive/dive/image\"\n)\n\ntype archiveResolver struct"
  },
  {
    "path": "dive/image/docker/build.go",
    "chars": 2341,
    "preview": "package docker\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/scylladb/go-set/strset\"\n\t\"github.com/spf13/afe"
  },
  {
    "path": "dive/image/docker/build_test.go",
    "chars": 6526,
    "preview": "package docker\n\nimport (\n\t\"github.com/stretchr/testify/require\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/spf13/afero\"\n\t"
  },
  {
    "path": "dive/image/docker/cli.go",
    "chars": 819,
    "preview": "package docker\n\nimport (\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/internal/log\"\n\t\"github.com/wagoodman/dive/internal/utils\"\n\t\""
  },
  {
    "path": "dive/image/docker/config.go",
    "chars": 1137,
    "preview": "package docker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\ntype config struct {\n\tHistory []historyEntry `json:\"history\"`\n\tRootF"
  },
  {
    "path": "dive/image/docker/docker_host_unix.go",
    "chars": 98,
    "preview": "//go:build !windows\n\npackage docker\n\nconst (\n\tdefaultDockerHost = \"unix:///var/run/docker.sock\"\n)\n"
  },
  {
    "path": "dive/image/docker/docker_host_windows.go",
    "chars": 79,
    "preview": "package docker\n\nconst (\n\tdefaultDockerHost = \"npipe:////.pipe/docker_engine\"\n)\n"
  },
  {
    "path": "dive/image/docker/engine_resolver.go",
    "chars": 5560,
    "preview": "package docker\n\nimport (\n\t\"fmt\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/wagoodman/dive/internal/bus/event/payload\"\n\t\"gith"
  },
  {
    "path": "dive/image/docker/image_archive.go",
    "chars": 9017,
    "preview": "package docker\n\nimport (\n\t\"archive/tar\"\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/fil"
  },
  {
    "path": "dive/image/docker/image_archive_analysis_test.go",
    "chars": 1270,
    "preview": "package docker\n\nimport (\n\t\"testing\"\n)\n\nfunc Test_Analysis(t *testing.T) {\n\n\ttable := map[string]struct {\n\t\tefficiency   "
  },
  {
    "path": "dive/image/docker/layer.go",
    "chars": 670,
    "preview": "package docker\n\nimport (\n\t\"strings\"\n\n\t\"github.com/wagoodman/dive/dive/filetree\"\n\t\"github.com/wagoodman/dive/dive/image\"\n"
  },
  {
    "path": "dive/image/docker/manifest.go",
    "chars": 427,
    "preview": "package docker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\ntype manifest struct {\n\tConfigPath    string   `json:\"Config\"`\n\tRepo"
  },
  {
    "path": "dive/image/docker/testing.go",
    "chars": 760,
    "preview": "package docker\n\nimport (\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/net/context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.c"
  },
  {
    "path": "dive/image/image.go",
    "chars": 156,
    "preview": "package image\n\nimport (\n\t\"github.com/wagoodman/dive/dive/filetree\"\n)\n\ntype Image struct {\n\tRequest string\n\tTrees   []*fi"
  },
  {
    "path": "dive/image/layer.go",
    "chars": 896,
    "preview": "package image\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/dustin/go-humanize\"\n\n\t\"github.com/wagoodman/dive/dive/filetree\"\n"
  },
  {
    "path": "dive/image/podman/build.go",
    "chars": 535,
    "preview": "//go:build linux || darwin\n\npackage podman\n\nimport (\n\t\"os\"\n)\n\nfunc buildImageFromCli(buildArgs []string) (string, error)"
  },
  {
    "path": "dive/image/podman/cli.go",
    "chars": 1426,
    "preview": "//go:build linux || darwin\n\npackage podman\n\nimport (\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/internal/log\"\n\t\"github.com/wagoo"
  },
  {
    "path": "dive/image/podman/resolver.go",
    "chars": 1544,
    "preview": "//go:build linux || darwin\n\npackage podman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/wagoodman/dive/dive/image\"\n\t\""
  },
  {
    "path": "dive/image/podman/resolver_unsupported.go",
    "chars": 749,
    "preview": "//go:build !linux && !darwin\n// +build !linux,!darwin\n\npackage podman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/wagoodma"
  },
  {
    "path": "dive/image/resolver.go",
    "chars": 333,
    "preview": "package image\n\nimport \"golang.org/x/net/context\"\n\ntype Resolver interface {\n\tName() string\n\tFetch(ctx context.Context, i"
  },
  {
    "path": "go.mod",
    "chars": 5485,
    "preview": "module github.com/wagoodman/dive\n\ngo 1.24\n\nrequire (\n\tgithub.com/anchore/clio v0.0.0-20250401141128-4c1d6bd1e872\n\tgithub"
  },
  {
    "path": "go.sum",
    "chars": 30819,
    "preview": "dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=\ndario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobSt"
  },
  {
    "path": "internal/bus/bus.go",
    "chars": 278,
    "preview": "package bus\n\nimport \"github.com/wagoodman/go-partybus\"\n\nvar publisher partybus.Publisher\n\nfunc Set(p partybus.Publisher)"
  },
  {
    "path": "internal/bus/event/event.go",
    "chars": 791,
    "preview": "package event\n\nimport (\n\t\"github.com/wagoodman/go-partybus\"\n)\n\nconst (\n\ttypePrefix = \"dive-cli\"\n\n\t// TaskStarted encompa"
  },
  {
    "path": "internal/bus/event/parser/parsers.go",
    "chars": 2383,
    "preview": "package parser\n\nimport (\n\t\"fmt\"\n\t\"github.com/wagoodman/dive/dive/image\"\n\t\"github.com/wagoodman/dive/internal/bus/event\"\n"
  },
  {
    "path": "internal/bus/event/payload/explore.go",
    "chars": 143,
    "preview": "package payload\n\nimport \"github.com/wagoodman/dive/dive/image\"\n\ntype Explore struct {\n\tAnalysis image.Analysis\n\tContent "
  },
  {
    "path": "internal/bus/event/payload/generic.go",
    "chars": 818,
    "preview": "package payload\n\nimport (\n\t\"context\"\n\t\"github.com/wagoodman/go-progress\"\n)\n\ntype genericProgressKey struct{}\n\nfunc SetGe"
  },
  {
    "path": "internal/bus/helpers.go",
    "chars": 1372,
    "preview": "package bus\n\nimport (\n\t\"github.com/wagoodman/dive/dive/image\"\n\t\"github.com/wagoodman/dive/internal/bus/event\"\n\t\"github.c"
  },
  {
    "path": "internal/log/log.go",
    "chars": 2177,
    "preview": "package log\n\nimport (\n\t\"github.com/anchore/go-logger\"\n\t\"github.com/anchore/go-logger/adapter/discard\"\n)\n\n// log is the s"
  },
  {
    "path": "internal/utils/format.go",
    "chars": 259,
    "preview": "package utils\n\nimport (\n\t\"strings\"\n)\n\n// CleanArgs trims the whitespace from the given set of strings.\nfunc CleanArgs(s "
  },
  {
    "path": "internal/utils/view.go",
    "chars": 474,
    "preview": "package utils\n\nimport (\n\t\"errors\"\n\t\"github.com/awesome-gocui/gocui\"\n\t\"github.com/wagoodman/dive/internal/log\"\n)\n\n// IsNe"
  }
]

About this extraction

This page contains the full source code of the wagoodman/dive GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 170 files (591.2 KB), approximately 206.5k tokens, and a symbol index with 716 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!