[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: ogham\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: exa is unmaintained\nabout: Please use the active fork eza instead. <https://github.com/eza-community/eza>\n---\n\nexa is unmaintained, please use the active fork eza instead. <https://github.com/eza-community/eza>\n\n---\n"
  },
  {
    "path": ".github/workflows/unit-tests.yml",
    "content": "name: Unit tests\n\non:\n  push:\n    branches: [ master ]\n    paths:\n      - '.github/workflows/*'\n      - 'src/**'\n      - 'Cargo.*'\n      - build.rs\n  pull_request:\n    branches: [ master ]\n    paths:\n      - '.github/workflows/*'\n      - 'src/**'\n      - 'Cargo.*'\n      - build.rs\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  unit-tests:\n    runs-on: ${{ matrix.os }}\n\n    continue-on-error: ${{ matrix.rust == 'nightly' }}\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest]\n        rust: [1.66.1, stable, beta, nightly]\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Install Rust toolchain\n        uses: dtolnay/rust-toolchain@v1\n        with:\n          toolchain: ${{ matrix.rust }}\n\n      - name: Install cargo-hack\n        run: cargo install cargo-hack@0.5.27\n\n      - name: Run unit tests\n        run: cargo hack test --feature-powerset\n"
  },
  {
    "path": ".gitignore",
    "content": "# Rust stuff\ntarget\n\n# Vagrant stuff\n.vagrant\n*.log\n\n# Compiled artifacts\n# (see devtools/*-package-for-*.sh)\n/exa-linux-x86_64\n/exa-linux-x86_64-*.zip\n/exa-macos-x86_64\n/exa-macos-x86_64-*.zip\n/MD5SUMS\n/SHA1SUMS\n\n# Snap stuff\nparts\nprime\nstage\n*.snap\n"
  },
  {
    "path": ".rustfmt.toml",
    "content": "disable_all_formatting = true\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"exa\"\ndescription = \"A modern replacement for ls\"\nauthors = [\"Benjamin Sago <ogham@bsago.me>\"]\ncategories = [\"command-line-utilities\"]\nedition = \"2021\"\nrust-version = \"1.66.1\"\nexclude = [\"/devtools/*\", \"/Justfile\", \"/Vagrantfile\", \"/screenshots.png\"]\nreadme = \"README.md\"\nhomepage = \"https://the.exa.website/\"\nlicense = \"MIT\"\nrepository = \"https://github.com/ogham/exa\"\nversion = \"0.10.1\"\n\n\n[[bin]]\nname = \"exa\"\n\n\n[dependencies]\nansi_term = \"0.12\"\nglob = \"0.3\"\nlazy_static = \"1.3\"\nlibc = \"0.2\"\nlocale = \"0.2\"\nlog = \"0.4\"\nnatord = \"1.0\"\nnum_cpus = \"1.10\"\nnumber_prefix = \"0.4\"\nscoped_threadpool = \"0.1\"\nterm_grid = \"0.2.0\"\nterminal_size = \"0.1.16\"\nunicode-width = \"0.1\"\nzoneinfo_compiled = \"0.5.1\"\n\n[target.'cfg(unix)'.dependencies]\nusers = \"0.11\"\n\n[dependencies.datetime]\nversion = \"0.5.2\"\ndefault-features = false\nfeatures = [\"format\"]\n\n[dependencies.git2]\nversion = \"0.13\"\noptional = true\ndefault-features = false\n\n[build-dependencies.datetime]\nversion = \"0.5.2\"\ndefault-features = false\n\n[features]\ndefault = [ \"git\" ]\ngit = [ \"git2\" ]\nvendored-openssl = [\"git2/vendored-openssl\"]\n\n\n# make dev builds faster by excluding debug symbols\n[profile.dev]\ndebug = false\n\n# use LTO for smaller binaries (that take longer to build)\n[profile.release]\nlto = true\n\n\n[package.metadata.deb]\nlicense-file = [ \"LICENCE\", \"4\" ]\ndepends = \"$auto\"\nextended-description = \"\"\"\nexa is a replacement for ls written in Rust.\n\"\"\"\nsection = \"utils\"\npriority = \"optional\"\nassets = [\n    [ \"target/release/exa\", \"/usr/bin/exa\", \"0755\" ],\n    [ \"target/release/../man/exa.1\", \"/usr/share/man/man1/exa.1\", \"0644\" ],\n    [ \"target/release/../man/exa_colors.5\", \"/usr/share/man/man5/exa_colors.5\", \"0644\" ],\n    [ \"completions/bash/exa\", \"/usr/share/bash-completion/completions/exa\", \"0644\" ],\n    [ \"completions/zsh/_exa\", \"/usr/share/zsh/site-functions/_exa\", \"0644\" ],\n    [ \"completions/fish/exa.fish\", \"/usr/share/fish/vendor_completions.d/exa.fish\", \"0644\" ],\n]\n"
  },
  {
    "path": "Justfile",
    "content": "all: build test\nall-release: build-release test-release\n\n\n#----------#\n# building #\n#----------#\n\n# compile the exa binary\n@build:\n    cargo build\n\n# compile the exa binary (in release mode)\n@build-release:\n    cargo build --release --verbose\n\n# produce an HTML chart of compilation timings\n@build-time:\n    cargo +nightly clean\n    cargo +nightly build -Z timings\n\n# check that the exa binary can compile\n@check:\n    cargo check\n\n\n#---------------#\n# running tests #\n#---------------#\n\n# run unit tests\n@test:\n    cargo test --workspace -- --quiet\n\n# run unit tests (in release mode)\n@test-release:\n    cargo test --workspace --release --verbose\n\n\n#------------------------#\n# running extended tests #\n#------------------------#\n\n# run extended tests\n@xtests:\n    xtests/run.sh\n\n# run extended tests (using the release mode exa)\n@xtests-release:\n    xtests/run.sh --release\n\n# display the number of extended tests that get run\n@count-xtests:\n    grep -F '[[cmd]]' -R xtests | wc -l\n\n\n#-----------------------#\n# code quality and misc #\n#-----------------------#\n\n# lint the code\n@clippy:\n    touch src/main.rs\n    cargo clippy\n\n# update dependency versions, and checks for outdated ones\n@update-deps:\n    cargo update\n    command -v cargo-outdated >/dev/null || (echo \"cargo-outdated not installed\" && exit 1)\n    cargo outdated\n\n# list unused dependencies\n@unused-deps:\n    command -v cargo-udeps >/dev/null || (echo \"cargo-udeps not installed\" && exit 1)\n    cargo +nightly udeps\n\n# check that every combination of feature flags is successful\n@check-features:\n    command -v cargo-hack >/dev/null || (echo \"cargo-hack not installed\" && exit 1)\n    cargo hack check --feature-powerset\n\n# build exa and run extended tests with features disabled\n@feature-checks *args:\n    cargo build --no-default-features\n    specsheet xtests/features/none.toml -shide {{args}} \\\n        -O cmd.target.exa=\"${CARGO_TARGET_DIR:-../../target}/debug/exa\"\n\n# print versions of the necessary build tools\n@versions:\n    rustc --version\n    cargo --version\n\n\n#---------------#\n# documentation #\n#---------------#\n\n# build the man pages\n@man:\n    mkdir -p \"${CARGO_TARGET_DIR:-target}/man\"\n    pandoc --standalone -f markdown -t man man/exa.1.md        > \"${CARGO_TARGET_DIR:-target}/man/exa.1\"\n    pandoc --standalone -f markdown -t man man/exa_colors.5.md > \"${CARGO_TARGET_DIR:-target}/man/exa_colors.5\"\n\n# build and preview the main man page (exa.1)\n@man-1-preview: man\n    man \"${CARGO_TARGET_DIR:-target}/man/exa.1\"\n\n# build and preview the colour configuration man page (exa_colors.5)\n@man-5-preview: man\n    man \"${CARGO_TARGET_DIR:-target}/man/exa_colors.5\"\n"
  },
  {
    "path": "LICENCE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Benjamin Sago\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# exa is unmaintained, use the [fork eza](https://github.com/eza-community/eza) instead.\n\n(This repository isn’t archived because the only person with the rights to do so is unreachable).\n\n---\n\n<div align=\"center\">\n\n# exa\n\n[exa](https://the.exa.website/) is a modern replacement for _ls_.\n\n**README Sections:** [Options](#options) — [Installation](#installation) — [Development](#development)\n\n[![Unit tests](https://github.com/ogham/exa/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/ogham/exa/actions/workflows/unit-tests.yml)\n</div>\n\n![Screenshots of exa](screenshots.png)\n\n---\n\n**exa** is a modern replacement for the venerable file-listing command-line program `ls` that ships with Unix and Linux operating systems, giving it more features and better defaults.\nIt uses colours to distinguish file types and metadata.\nIt knows about symlinks, extended attributes, and Git.\nAnd it’s **small**, **fast**, and just **one single binary**.\n\nBy deliberately making some decisions differently, exa attempts to be a more featureful, more user-friendly version of `ls`.\nFor more information, see [exa’s website](https://the.exa.website/).\n\n\n---\n\n<a id=\"options\">\n<h1>Command-line options</h1>\n</a>\n\nexa’s options are almost, but not quite, entirely unlike `ls`’s.\n\n### Display options\n\n- **-1**, **--oneline**: display one entry per line\n- **-G**, **--grid**: display entries as a grid (default)\n- **-l**, **--long**: display extended details and attributes\n- **-R**, **--recurse**: recurse into directories\n- **-T**, **--tree**: recurse into directories as a tree\n- **-x**, **--across**: sort the grid across, rather than downwards\n- **-F**, **--classify**: display type indicator by file names\n- **--colo[u]r**: when to use terminal colours\n- **--colo[u]r-scale**: highlight levels of file sizes distinctly\n- **--icons**: display icons\n- **--no-icons**: don't display icons (always overrides --icons)\n\n### Filtering options\n\n- **-a**, **--all**: show hidden and 'dot' files\n- **-d**, **--list-dirs**: list directories like regular files\n- **-L**, **--level=(depth)**: limit the depth of recursion\n- **-r**, **--reverse**: reverse the sort order\n- **-s**, **--sort=(field)**: which field to sort by\n- **--group-directories-first**: list directories before other files\n- **-D**, **--only-dirs**: list only directories\n- **--git-ignore**: ignore files mentioned in `.gitignore`\n- **-I**, **--ignore-glob=(globs)**: glob patterns (pipe-separated) of files to ignore\n\nPass the `--all` option twice to also show the `.` and `..` directories.\n\n### Long view options\n\nThese options are available when running with `--long` (`-l`):\n\n- **-b**, **--binary**: list file sizes with binary prefixes\n- **-B**, **--bytes**: list file sizes in bytes, without any prefixes\n- **-g**, **--group**: list each file’s group\n- **-h**, **--header**: add a header row to each column\n- **-H**, **--links**: list each file’s number of hard links\n- **-i**, **--inode**: list each file’s inode number\n- **-m**, **--modified**: use the modified timestamp field\n- **-S**, **--blocks**: list each file’s number of file system blocks\n- **-t**, **--time=(field)**: which timestamp field to use\n- **-u**, **--accessed**: use the accessed timestamp field\n- **-U**, **--created**: use the created timestamp field\n- **-@**, **--extended**: list each file’s extended attributes and sizes\n- **--changed**: use the changed timestamp field\n- **--git**: list each file’s Git status, if tracked or ignored\n- **--time-style**: how to format timestamps\n- **--no-permissions**: suppress the permissions field\n- **--octal-permissions**: list each file's permission in octal format\n- **--no-filesize**: suppress the filesize field\n- **--no-user**: suppress the user field\n- **--no-time**: suppress the time field\n\nSome of the options accept parameters:\n\n- Valid **--color** options are **always**, **automatic**, and **never**.\n- Valid sort fields are **accessed**, **changed**, **created**, **extension**, **Extension**, **inode**, **modified**, **name**, **Name**, **size**, **type**, and **none**. Fields starting with a capital letter sort uppercase before lowercase. The modified field has the aliases **date**, **time**, and **newest**, while its reverse has the aliases **age** and **oldest**.\n- Valid time fields are **modified**, **changed**, **accessed**, and **created**.\n- Valid time styles are **default**, **iso**, **long-iso**, and **full-iso**.\n\n\n---\n\n<a id=\"installation\">\n<h1>Installation</h1>\n</a>\n\nexa is available for macOS and Linux.\nMore information on how to install exa is available on [the Installation page](https://the.exa.website/install).\n\n### Alpine Linux\n\nOn Alpine Linux, [enable community repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) and install the [`exa`](https://pkgs.alpinelinux.org/package/edge/community/x86_64/exa) package.\n\n    apk add exa\n\n### Arch Linux\n\nOn Arch, install the [`exa`](https://www.archlinux.org/packages/community/x86_64/exa/) package.\n\n    pacman -S exa\n\n### Android / Termux\n\nOn Android / Termux, install the [`exa`](https://github.com/termux/termux-packages/tree/master/packages/exa) package.\n\n    pkg install exa\n\n### Debian\n\nOn Debian, install the [`exa`](https://packages.debian.org/stable/exa) package.\n\n    apt install exa\n\n### Fedora\n\nOn Fedora, install the [`exa`](https://src.fedoraproject.org/modules/exa) package.\n\n    dnf install exa\n\n### Gentoo\n\nOn Gentoo, install the [`sys-apps/exa`](https://packages.gentoo.org/packages/sys-apps/exa) package.\n\n    emerge sys-apps/exa\n\n### Homebrew\n\nIf you’re using [Homebrew](https://brew.sh/) on macOS, install the [`exa`](http://formulae.brew.sh/formula/exa) formula.\n\n    brew install exa\n\n### MacPorts\n\nIf you're using [MacPorts](https://www.macports.org/) on macOS, install the [`exa`](https://ports.macports.org/port/exa/summary) port.\n\n    port install exa\n\n### Nix\n\nOn nixOS, install the [`exa`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/misc/exa/default.nix) package.\n\n    nix-env -i exa\n\n### openSUSE\n\nOn openSUSE, install the [`exa`](https://software.opensuse.org/package/exa) package.\n\n    zypper install exa\n\n### Ubuntu\n\nOn Ubuntu 20.10 (Groovy Gorilla) and later, install the [`exa`](https://packages.ubuntu.com/jammy/exa) package.\n\n    sudo apt install exa\n\n### Void Linux\n\nOn Void Linux, install the [`exa`](https://github.com/void-linux/void-packages/blob/master/srcpkgs/exa/template) package.\n\n    xbps-install -S exa\n\n### Manual installation from GitHub\n\nCompiled binary versions of exa are uploaded to GitHub when a release is made.\nYou can install exa manually by [downloading a release](https://github.com/ogham/exa/releases), extracting it, and copying the binary to a directory in your `$PATH`, such as `/usr/local/bin`.\n\nFor more information, see the [Manual Installation page](https://the.exa.website/install/linux#manual).\n\n### Cargo\n\nIf you already have a Rust environment set up, you can use the `cargo install` command:\n\n    cargo install exa\n\nCargo will build the `exa` binary and place it in `$HOME/.cargo`.\n\nTo build without Git support, run `cargo install --no-default-features exa` is also available, if the requisite dependencies are not installed.\n\n\n---\n\n<a id=\"development\">\n<h1>Development\n\n<a href=\"https://blog.rust-lang.org/2023/01/10/Rust-1.66.1.html\">\n    <img src=\"https://img.shields.io/badge/rustc-1.66.1+-lightgray.svg\" alt=\"Rust 1.66.1+\" />\n</a>\n\n<a href=\"https://github.com/ogham/exa/blob/master/LICENCE\">\n    <img src=\"https://img.shields.io/badge/licence-MIT-green\" alt=\"MIT Licence\" />\n</a>\n</h1></a>\n\nexa is written in [Rust](https://www.rust-lang.org/).\nYou will need rustc version 1.66.1 or higher.\nThe recommended way to install Rust for development is from the [official download page](https://www.rust-lang.org/tools/install), using rustup.\n\nOnce Rust is installed, you can compile exa with Cargo:\n\n    cargo build\n    cargo test\n\n- The [just](https://github.com/casey/just) command runner can be used to run some helpful development commands, in a manner similar to `make`.\nRun `just --list` to get an overview of what’s available.\n\n- If you are compiling a copy for yourself, be sure to run `cargo build --release` or `just build-release` to benefit from release-mode optimisations.\nCopy the resulting binary, which will be in the `target/release` directory, into a folder in your `$PATH`.\n`/usr/local/bin` is usually a good choice.\n\n- To compile and install the manual pages, you will need [pandoc](https://pandoc.org/).\nThe `just man` command will compile the Markdown into manual pages, which it will place in the `target/man` directory.\nTo use them, copy them into a directory that `man` will read.\n`/usr/local/share/man` is usually a good choice.\n\n- exa depends on [libgit2](https://github.com/rust-lang/git2-rs) for certain features.\nIf you’re unable to compile libgit2, you can opt out of Git support by running `cargo build --no-default-features`.\n\n- If you intend to compile for musl, you will need to use the flag `vendored-openssl` if you want to get the Git feature working.\nThe full command is `cargo build --release --target=x86_64-unknown-linux-musl --features vendored-openssl,git`.\n\nFor more information, see the [Building from Source page](https://the.exa.website/install/source).\n\n\n### Testing with Vagrant\n\nexa uses [Vagrant][] to configure virtual machines for testing.\n\nPrograms such as exa that are basically interfaces to the system are [notoriously difficult to test][testing].\nAlthough the internal components have unit tests, it’s impossible to do a complete end-to-end test without mandating the current user’s name, the time zone, the locale, and directory structure to test.\n(And yes, these tests are worth doing. I have missed an edge case on many an occasion.)\n\nThe initial attempt to solve the problem was just to create a directory of “awkward” test cases, run exa on it, and make sure it produced the correct output.\nBut even this output would change if, say, the user’s locale formats dates in a different way.\nThese can be mocked inside the code, but at the cost of making that code more complicated to read and understand.\n\nAn alternative solution is to fake *everything*: create a virtual machine with a known state and run the tests on *that*.\nThis is what Vagrant does.\nAlthough it takes a while to download and set up, it gives everyone the same development environment to test for any obvious regressions.\n\n[Vagrant]: https://www.vagrantup.com/\n[testing]: https://eev.ee/blog/2016/08/22/testing-for-people-who-hate-testing/#troublesome-cases\n\nFirst, initialise the VM:\n\n    host$ vagrant up\n\nThe first command downloads the virtual machine image, and then runs our provisioning script, which installs Rust and exa’s build-time dependencies, configures the environment, and generates some awkward files and folders to use as test cases.\nOnce this is done, you can SSH in, and build and test:\n\n    host$ vagrant ssh\n    vm$ cd /vagrant\n    vm$ cargo build\n    vm$ ./xtests/run\n    All the tests passed!\n\nOf course, the drawback of having a standard development environment is that you stop noticing bugs that occur outside of it.\nFor this reason, Vagrant isn’t a *necessary* development step — it’s there if you’d like to use it, but exa still gets used and tested on other platforms.\nIt can still be built and compiled on any target triple that it supports, VM or no VM, with `cargo build` and `cargo test`.\n"
  },
  {
    "path": "Vagrantfile",
    "content": "Vagrant.configure(2) do |config|\n\n  # We use Ubuntu instead of Debian because the image comes with two-way\n  # shared folder support by default.\n  UBUNTU = 'hashicorp/bionic64'\n\n  config.vm.define(:exa) do |config|\n    config.vm.provider :virtualbox do |v|\n      v.name = 'exa'\n      v.memory = 2048\n      v.cpus = `nproc`.chomp.to_i\n    end\n\n    config.vm.provider :vmware_desktop do |v|\n      v.vmx['memsize'] = '2048'\n      v.vmx['numvcpus'] = `nproc`.chomp\n    end\n\n    config.vm.box = UBUNTU\n    config.vm.hostname = 'exa'\n\n\n    # Make sure we know the VM image’s default user name. The ‘cassowary’ user\n    # (specified later) is used for most of the test *output*, but we still\n    # need to know where the ‘target’ and ‘.cargo’ directories go.\n    developer = 'vagrant'\n\n\n    # Install the dependencies needed for exa to build, as quietly as\n    # apt can do.\n    config.vm.provision :shell, privileged: true, inline: <<-EOF\n      if hash fish &>/dev/null; then\n        echo \"Tools are already installed\"\n      else\n        trap 'exit' ERR\n        echo \"Installing tools\"\n        apt-get update -qq\n        apt-get install -qq -o=Dpkg::Use-Pty=0 \\\n          git gcc curl attr libgit2-dev zip \\\n          fish zsh bash bash-completion\n      fi\n    EOF\n\n\n    # Install Rust.\n    # This is done as vagrant, not root, because it’s vagrant\n    # who actually uses it. Sent to /dev/null because the progress\n    # bar produces a ton of output.\n    config.vm.provision :shell, privileged: false, inline: <<-EOF\n      if hash rustc &>/dev/null; then\n        echo \"Rust is already installed\"\n      else\n        trap 'exit' ERR\n        echo \"Installing Rust\"\n        curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --profile minimal --component rustc,rust-std,cargo,clippy -y > /dev/null\n        source $HOME/.cargo/env\n        echo \"Installing cargo-hack\"\n        cargo install -q cargo-hack\n        echo \"Installing specsheet\"\n        cargo install -q --git https://github.com/ogham/specsheet\n      fi\n    EOF\n\n\n    # Privileged installation and setup scripts.\n    config.vm.provision :shell, privileged: true, inline: <<-EOF\n\n      # Install Just, the command runner.\n      if hash just &>/dev/null; then\n        echo \"just is already installed\"\n      else\n        trap 'exit' ERR\n        echo \"Installing just\"\n        wget -q \"https://github.com/casey/just/releases/download/v0.8.3/just-v0.8.3-x86_64-unknown-linux-musl.tar.gz\"\n        tar -xf \"just-v0.8.3-x86_64-unknown-linux-musl.tar.gz\"\n        cp just /usr/local/bin\n      fi\n\n      # Guarantee that the timezone is UTC — some of the tests\n      # depend on this (for now).\n      timedatectl set-timezone UTC\n\n\n      # Use a different ‘target’ directory on the VM than on the host.\n      # By default it just uses the one in /vagrant/target, which can\n      # cause problems if it has different permissions than the other\n      # directories, or contains object files compiled for the host.\n      echo 'PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/#{developer}/.cargo/bin\"' > /etc/environment\n      echo 'CARGO_TARGET_DIR=\"/home/#{developer}/target\"'                                                     >> /etc/environment\n\n\n      # Create a variety of misc scripts.\n\n      ln -sf /vagrant/devtools/dev-run-debug.sh   /usr/bin/exa\n      ln -sf /vagrant/devtools/dev-run-release.sh /usr/bin/rexa\n\n      echo -e \"#!/bin/sh\\ncargo build --manifest-path /vagrant/Cargo.toml \\\\$@\" > /usr/bin/build-exa\n      ln -sf /usr/bin/build-exa /usr/bin/b\n\n      echo -e \"#!/bin/sh\\ncargo test --manifest-path /vagrant/Cargo.toml \\\\$@ -- --quiet\" > /usr/bin/test-exa\n      ln -sf /usr/bin/test-exa /usr/bin/t\n\n      echo -e \"#!/bin/sh\\n/vagrant/xtests/run.sh\" > /usr/bin/run-xtests\n      ln -sf /usr/bin/run-xtests /usr/bin/x\n\n      echo -e \"#!/bin/sh\\nbuild-exa && test-exa && run-xtests\" > /usr/bin/compile-exa\n      ln -sf /usr/bin/compile-exa /usr/bin/c\n\n      echo -e \"#!/bin/sh\\nbash /vagrant/devtools/dev-package-for-linux.sh \\\\$@\" > /usr/bin/package-exa\n      echo -e \"#!/bin/sh\\ncat /etc/motd\" > /usr/bin/halp\n\n      chmod +x /usr/bin/{exa,rexa,b,t,x,c,build-exa,test-exa,run-xtests,compile-exa,package-exa,halp}\n\n\n      # Configure the welcoming text that gets shown:\n\n      # Capture the help text so it gets displayed first\n      rm -f /etc/update-motd.d/*\n      bash /vagrant/devtools/dev-help.sh > /etc/motd\n\n      # Tell bash to execute a bunch of stuff when a session starts\n      echo \"source /vagrant/devtools/dev-bash.sh\" > /home/#{developer}/.bash_profile\n      chown #{developer} /home/#{developer}/.bash_profile\n\n      # Disable last login date in sshd\n      sed -i '/PrintLastLog yes/c\\PrintLastLog no' /etc/ssh/sshd_config\n      systemctl restart sshd\n\n\n      # Link the completion files so they’re “installed”:\n\n      # bash\n      test -h /etc/bash_completion.d/exa \\\n        || ln -s /vagrant/contrib/completions.bash /etc/bash_completion.d/exa\n\n      # zsh\n      test -h /usr/share/zsh/vendor-completions/_exa \\\n        || ln -s /vagrant/contrib/completions.zsh /usr/share/zsh/vendor-completions/_exa\n\n      # fish\n      test -h /usr/share/fish/completions/exa.fish \\\n        || ln -s /vagrant/contrib/completions.fish /usr/share/fish/completions/exa.fish\n    EOF\n\n\n    # Install kcov for test coverage\n    # This doesn’t run coverage over the xtests so it’s less useful for now\n    if ENV.key?('INSTALL_KCOV')\n      config.vm.provision :shell, privileged: false, inline: <<-EOF\n        trap 'exit' ERR\n\n        test -e ~/.cargo/bin/cargo-kcov \\\n          || cargo install cargo-kcov\n\n        sudo apt-get install -qq -o=Dpkg::Use-Pty=0 -y \\\n          cmake g++ pkg-config \\\n          libcurl4-openssl-dev libdw-dev binutils-dev libiberty-dev\n\n        cargo kcov --print-install-kcov-sh | sudo sh\n      EOF\n    end\n\n    config.vm.provision :shell, privileged: true,  path: 'devtools/dev-set-up-environment.sh'\n    config.vm.provision :shell, privileged: false, path: 'devtools/dev-create-test-filesystem.sh'\n  end\nend\n"
  },
  {
    "path": "build.rs",
    "content": "/// The version string isn’t the simplest: we want to show the version,\n/// current Git hash, and compilation date when building *debug* versions, but\n/// just the version for *release* versions so the builds are reproducible.\n///\n/// This script generates the string from the environment variables that Cargo\n/// adds (http://doc.crates.io/environment-variables.html) and runs `git` to\n/// get the SHA1 hash. It then writes the string into a file, which exa then\n/// includes at build-time.\n///\n/// - https://stackoverflow.com/q/43753491/3484614\n/// - https://crates.io/crates/vergen\n\nuse std::env;\nuse std::fs::File;\nuse std::io::{self, Write};\nuse std::path::PathBuf;\n\nuse datetime::{LocalDateTime, ISO};\n\n\n/// The build script entry point.\nfn main() -> io::Result<()> {\n    #![allow(clippy::write_with_newline)]\n\n    let tagline = \"exa - list files on the command-line\";\n    let url     = \"https://the.exa.website/\";\n\n    let ver =\n        if is_debug_build() {\n            format!(\"{}\\nv{} \\\\1;31m(pre-release debug build!)\\\\0m\\n\\\\1;4;34m{}\\\\0m\", tagline, version_string(), url)\n        }\n        else if is_development_version() {\n            format!(\"{}\\nv{} [{}] built on {} \\\\1;31m(pre-release!)\\\\0m\\n\\\\1;4;34m{}\\\\0m\", tagline, version_string(), git_hash(), build_date(), url)\n        }\n        else {\n            format!(\"{}\\nv{}\\n\\\\1;4;34m{}\\\\0m\", tagline, version_string(), url)\n        };\n\n    // We need to create these files in the Cargo output directory.\n    let out = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n    let path = &out.join(\"version_string.txt\");\n\n    // Bland version text\n    let mut f = File::create(path).unwrap_or_else(|_| { panic!(\"{}\", path.to_string_lossy().to_string()) });\n    writeln!(f, \"{}\", strip_codes(&ver))?;\n\n    Ok(())\n}\n\n/// Removes escape codes from a string.\nfn strip_codes(input: &str) -> String {\n    input.replace(\"\\\\0m\", \"\")\n         .replace(\"\\\\1;31m\", \"\")\n         .replace(\"\\\\1;4;34m\", \"\")\n}\n\n/// Retrieve the project’s current Git hash, as a string.\nfn git_hash() -> String {\n    use std::process::Command;\n\n    String::from_utf8_lossy(\n        &Command::new(\"git\")\n            .args(&[\"rev-parse\", \"--short\", \"HEAD\"])\n            .output().unwrap()\n            .stdout).trim().to_string()\n}\n\n/// Whether we should show pre-release info in the version string.\n///\n/// Both weekly releases and actual releases are --release releases,\n/// but actual releases will have a proper version number.\nfn is_development_version() -> bool {\n    cargo_version().ends_with(\"-pre\") || env::var(\"PROFILE\").unwrap() == \"debug\"\n}\n\n/// Whether we are building in debug mode.\nfn is_debug_build() -> bool {\n    env::var(\"PROFILE\").unwrap() == \"debug\"\n}\n\n/// Retrieves the [package] version in Cargo.toml as a string.\nfn cargo_version() -> String {\n    env::var(\"CARGO_PKG_VERSION\").unwrap()\n}\n\n/// Returns the version and build parameters string.\nfn version_string() -> String {\n    let mut ver = cargo_version();\n\n    let feats = nonstandard_features_string();\n    if ! feats.is_empty() {\n        ver.push_str(&format!(\" [{}]\", &feats));\n    }\n\n    ver\n}\n\n/// Finds whether a feature is enabled by examining the Cargo variable.\nfn feature_enabled(name: &str) -> bool {\n    env::var(&format!(\"CARGO_FEATURE_{}\", name))\n        .map(|e| ! e.is_empty())\n        .unwrap_or(false)\n}\n\n/// A comma-separated list of non-standard feature choices.\nfn nonstandard_features_string() -> String {\n    let mut s = Vec::new();\n\n    if feature_enabled(\"GIT\") {\n        s.push(\"+git\");\n    }\n    else {\n        s.push(\"-git\");\n    }\n\n    s.join(\", \")\n}\n\n/// Formats the current date as an ISO 8601 string.\nfn build_date() -> String {\n    let now = LocalDateTime::now();\n    format!(\"{}\", now.date().iso())\n}\n"
  },
  {
    "path": "completions/bash/exa",
    "content": "_exa()\n{\n    cur=${COMP_WORDS[COMP_CWORD]}\n    prev=${COMP_WORDS[COMP_CWORD-1]}\n\n    case \"$prev\" in\n        -'?'|--help|-v|--version)\n            return\n            ;;\n\n        --colour)\n            COMPREPLY=( $( compgen -W 'always auto never' -- \"$cur\" ) )\n            return\n            ;;\n\n        -L|--level)\n            COMPREPLY=( $( compgen -W '{0..9}' -- \"$cur\" ) )\n            return\n            ;;\n\n        -s|--sort)\n            COMPREPLY=( $( compgen -W 'name filename Name Filename size filesize extension Extension date time modified changed accessed created type inode oldest newest age none --' -- \"$cur\" ) )\n            return\n            ;;\n\n        -t|--time)\n            COMPREPLY=( $( compgen -W 'modified changed accessed created --' -- \"$cur\" ) )\n            return\n            ;;\n\n        --time-style)\n            COMPREPLY=( $( compgen -W 'default iso long-iso full-iso --' -- \"$cur\" ) )\n            return\n            ;;\n    esac\n\n    case \"$cur\" in\n        # _parse_help doesn’t pick up short options when they are on the same line than long options\n        --*)\n            # colo[u]r isn’t parsed correctly so we filter these options out and add them by hand\n            parse_help=$( exa --help | grep -oE ' (\\-\\-[[:alnum:]@-]+)' | tr -d ' ' | grep -v '\\-\\-colo' )\n            completions=$( echo '--color --colour --color-scale --colour-scale' $parse_help )\n            COMPREPLY=( $( compgen -W \"$completions\" -- \"$cur\" ) )\n            ;;\n\n        -*)\n            completions=$( exa --help | grep -oE ' (\\-[[:alnum:]@])' | tr -d ' ' )\n            COMPREPLY=( $( compgen -W \"$completions\" -- \"$cur\" ) )\n            ;;\n\n        *)\n            _filedir\n            ;;\n    esac\n} &&\ncomplete -o filenames -o bashdefault -F _exa exa\n"
  },
  {
    "path": "completions/fish/exa.fish",
    "content": "# Meta-stuff\ncomplete -c exa -s 'v' -l 'version' -d \"Show version of exa\"\ncomplete -c exa -s '?' -l 'help'    -d \"Show list of command-line options\"\n\n# Display options\ncomplete -c exa -s '1' -l 'oneline'      -d \"Display one entry per line\"\ncomplete -c exa -s 'l' -l 'long'         -d \"Display extended file metadata as a table\"\ncomplete -c exa -s 'G' -l 'grid'         -d \"Display entries in a grid\"\ncomplete -c exa -s 'x' -l 'across'       -d \"Sort the grid across, rather than downwards\"\ncomplete -c exa -s 'R' -l 'recurse'      -d \"Recurse into directories\"\ncomplete -c exa -s 'T' -l 'tree'         -d \"Recurse into directories as a tree\"\ncomplete -c exa -s 'F' -l 'classify'     -d \"Display type indicator by file names\"\ncomplete -c exa        -l 'color' \\\n                       -l 'colour'       -d \"When to use terminal colours\" -x -a \"\n    always\\t'Always use colour'\n    auto\\t'Use colour if standard output is a terminal'\n    never\\t'Never use colour'\n\"\ncomplete -c exa        -l 'color-scale' \\\n                       -l 'colour-scale' -d \"Highlight levels of file sizes distinctly\"\ncomplete -c exa        -l 'icons'        -d \"Display icons\"\ncomplete -c exa        -l 'no-icons'     -d \"Don't display icons\"\n\n# Filtering and sorting options\ncomplete -c exa -l 'group-directories-first' -d \"Sort directories before other files\"\ncomplete -c exa -l 'git-ignore'           -d \"Ignore files mentioned in '.gitignore'\"\ncomplete -c exa -s 'a' -l 'all'       -d \"Show hidden and 'dot' files\"\ncomplete -c exa -s 'd' -l 'list-dirs' -d \"List directories like regular files\"\ncomplete -c exa -s 'L' -l 'level'     -d \"Limit the depth of recursion\" -x -a \"1 2 3 4 5 6 7 8 9\"\ncomplete -c exa -s 'r' -l 'reverse'   -d \"Reverse the sort order\"\ncomplete -c exa -s 's' -l 'sort'      -d \"Which field to sort by\" -x -a \"\n    accessed\\t'Sort by file accessed time'\n    age\\t'Sort by file modified time (newest first)'\n    changed\\t'Sort by changed time'\n    created\\t'Sort by file modified time'\n    date\\t'Sort by file modified time'\n    ext\\t'Sort by file extension'\n    Ext\\t'Sort by file extension (uppercase first)'\n    extension\\t'Sort by file extension'\n    Extension\\t'Sort by file extension (uppercase first)'\n    filename\\t'Sort by filename'\n    Filename\\t'Sort by filename (uppercase first)'\n    inode\\t'Sort by file inode'\n    modified\\t'Sort by file modified time'\n    name\\t'Sort by filename'\n    Name\\t'Sort by filename (uppercase first)'\n    newest\\t'Sort by file modified time (newest first)'\n    none\\t'Do not sort files at all'\n    oldest\\t'Sort by file modified time'\n    size\\t'Sort by file size'\n    time\\t'Sort by file modified time'\n    type\\t'Sort by file type'\n\"\n\ncomplete -c exa -s 'I' -l 'ignore-glob' -d \"Ignore files that match these glob patterns\" -r\ncomplete -c exa -s 'D' -l 'only-dirs'   -d \"List only directories\"\n\n# Long view options\ncomplete -c exa -s 'b' -l 'binary'   -d \"List file sizes with binary prefixes\"\ncomplete -c exa -s 'B' -l 'bytes'    -d \"List file sizes in bytes, without any prefixes\"\ncomplete -c exa -s 'g' -l 'group'    -d \"List each file's group\"\ncomplete -c exa -s 'h' -l 'header'   -d \"Add a header row to each column\"\ncomplete -c exa -s 'H' -l 'links'    -d \"List each file's number of hard links\"\ncomplete -c exa -s 'i' -l 'inode'    -d \"List each file's inode number\"\ncomplete -c exa -s 'S' -l 'blocks'   -d \"List each file's number of filesystem blocks\"\ncomplete -c exa -s 't' -l 'time'     -d \"Which timestamp field to list\" -x -a \"\n    modified\\t'Display modified time'\n    changed\\t'Display changed time'\n    accessed\\t'Display accessed time'\n    created\\t'Display created time'\n\"\ncomplete -c exa -s 'm' -l 'modified'      -d \"Use the modified timestamp field\"\ncomplete -c exa -s 'n' -l 'numeric'       -d \"List numeric user and group IDs.\"\ncomplete -c exa        -l 'changed'       -d \"Use the changed timestamp field\"\ncomplete -c exa -s 'u' -l 'accessed'      -d \"Use the accessed timestamp field\"\ncomplete -c exa -s 'U' -l 'created'       -d \"Use the created timestamp field\"\ncomplete -c exa        -l 'time-style'    -d \"How to format timestamps\" -x -a \"\n    default\\t'Use the default time style'\n    iso\\t'Display brief ISO timestamps'\n    long-iso\\t'Display longer ISO timestaps, up to the minute'\n    full-iso\\t'Display full ISO timestamps, up to the nanosecond'\n\"\ncomplete -c exa        -l 'no-permissions' -d \"Suppress the permissions field\"\ncomplete -c exa        -l 'octal-permissions' -d \"List each file's permission in octal format\"\ncomplete -c exa        -l 'no-filesize'    -d \"Suppress the filesize field\"\ncomplete -c exa        -l 'no-user'        -d \"Suppress the user field\"\ncomplete -c exa        -l 'no-time'        -d \"Suppress the time field\"\n\n# Optional extras\ncomplete -c exa -l 'git' -d \"List each file's Git status, if tracked\"\ncomplete -c exa -s '@' -l 'extended' -d \"List each file's extended attributes and sizes\"\n"
  },
  {
    "path": "completions/zsh/_exa",
    "content": "#compdef exa\n\n# Save this file as _exa in /usr/local/share/zsh/site-functions or in any\n# other folder in $fpath.  E.g. save it in a folder called ~/.zfunc and add a\n# line containing `fpath=(~/.zfunc $fpath)` somewhere before `compinit` in your\n# ~/.zshrc.\n\n__exa() {\n    # Give completions using the `_arguments` utility function with\n    # `-s` for option stacking like `exa -ab` for `exa -a -b` and\n    # `-S` for delimiting options with `--` like in `exa -- -a`.\n    _arguments -s -S \\\n        \"(- *)\"{-v,--version}\"[Show version of exa]\" \\\n        \"(- *)\"{-'\\?',--help}\"[Show list of command-line options]\" \\\n        {-1,--oneline}\"[Display one entry per line]\" \\\n        {-l,--long}\"[Display extended file metadata as a table]\" \\\n        {-G,--grid}\"[Display entries as a grid]\" \\\n        {-x,--across}\"[Sort the grid across, rather than downwards]\" \\\n        {-R,--recurse}\"[Recurse into directories]\" \\\n        {-T,--tree}\"[Recurse into directories as a tree]\" \\\n        {-F,--classify}\"[Display type indicator by file names]\" \\\n        --colo{,u}r=\"[When to use terminal colours]:(when):(always auto never)\" \\\n        --colo{,u}r-scale\"[Highlight levels of file sizes distinctly]\" \\\n        --icons\"[Display icons]\" \\\n        --no-icons\"[Hide icons]\" \\\n        --group-directories-first\"[Sort directories before other files]\" \\\n        --git-ignore\"[Ignore files mentioned in '.gitignore']\" \\\n        {-a,--all}\"[Show hidden and 'dot' files]\" \\\n        {-d,--list-dirs}\"[List directories like regular files]\" \\\n        {-D,--only-dirs}\"[List only directories]\" \\\n        {-L,--level}\"+[Limit the depth of recursion]\" \\\n        {-r,--reverse}\"[Reverse the sort order]\" \\\n        {-s,--sort}=\"[Which field to sort by]:(sort field):(accessed age changed created date extension Extension filename Filename inode modified oldest name Name newest none size time type)\" \\\n        {-I,--ignore-glob}\"[Ignore files that match these glob patterns]\" \\\n        {-b,--binary}\"[List file sizes with binary prefixes]\" \\\n        {-B,--bytes}\"[List file sizes in bytes, without any prefixes]\" \\\n        --changed\"[Use the changed timestamp field]\" \\\n        {-g,--group}\"[List each file's group]\" \\\n        {-h,--header}\"[Add a header row to each column]\" \\\n        {-H,--links}\"[List each file's number of hard links]\" \\\n        {-i,--inode}\"[List each file's inode number]\" \\\n        {-m,--modified}\"[Use the modified timestamp field]\" \\\n        {-n,--numeric}\"[List numeric user and group IDs.]\" \\\n        {-S,--blocks}\"[List each file's number of filesystem blocks]\" \\\n        {-t,--time}=\"[Which time field to show]:(time field):(accessed changed created modified)\" \\\n        --time-style=\"[How to format timestamps]:(time style):(default iso long-iso full-iso)\" \\\n        --no-permissions\"[Suppress the permissions field]\" \\\n        --octal-permissions\"[List each file's permission in octal format]\" \\\n        --no-filesize\"[Suppress the filesize field]\" \\\n        --no-user\"[Suppress the user field]\" \\\n        --no-time\"[Suppress the time field]\" \\\n        {-u,--accessed}\"[Use the accessed timestamp field]\" \\\n        {-U,--created}\"[Use the created timestamp field]\" \\\n        --git\"[List each file's Git status, if tracked]\" \\\n        {-@,--extended}\"[List each file's extended attributes and sizes]\" \\\n        '*:filename:_files'\n}\n\n__exa\n"
  },
  {
    "path": "devtools/README.md",
    "content": "## exa › development tools\n\nThese scripts deal with things like packaging release-worthy versions of exa.\n\nThey are **not general-purpose scripts** that you’re able to run from your main computer! They’re intended to be run from the Vagrant machine.\n"
  },
  {
    "path": "devtools/dev-bash.sh",
    "content": "# This file gets executed when a user starts a `bash` shell, usually because\n# they’ve just started a new Vagrant session with `vagrant ssh`. It configures\n# some (but not all) of the commands that you can use.\n\n\n# Display the installed versions of tools.\n# help banner\nbash /vagrant/devtools/dev-versions.sh\n\n\n# Configure the Cool Prompt™ (not actually trademarked).\n# The Cool Prompt tells you whether you’re in debug or strict mode, whether\n# you have colours configured, and whether your last command failed.\nnonzero_return() { RETVAL=$?; [ \"$RETVAL\" -ne 0 ] && echo \"$RETVAL \"; }\ndebug_mode()  { [ \"$EXA_DEBUG\" == \"trace\" ] && echo -n \"trace-\"; [ -n \"$EXA_DEBUG\" ] && echo \"debug \"; }\nstrict_mode() { [ -n \"$EXA_STRICT\" ] && echo \"strict \"; }\nlsc_mode()    { [ -n \"$LS_COLORS\" ]  && echo \"lsc \"; }\nexac_mode()   { [ -n \"$EXA_COLORS\" ] && echo \"exac \"; }\nexport PS1=\"\\[\\e[1;36m\\]\\h \\[\\e[32m\\]\\w \\[\\e[31m\\]\\`nonzero_return\\`\\[\\e[35m\\]\\`debug_mode\\`\\[\\e[32m\\]\\`lsc_mode\\`\\[\\e[1;32m\\]\\`exac_mode\\`\\[\\e[33m\\]\\`strict_mode\\`\\[\\e[36m\\]\\\\$\\[\\e[0m\\] \"\n\n\n# The ‘debug’ function lets you switch debug mode on and off.\n# Turn it on if you need to see exa’s debugging logs.\ndebug() {\n  case \"$1\" in\n    \"\"|\"on\")  export EXA_DEBUG=1 ;;\n    \"off\")    export EXA_DEBUG= ;;\n    \"trace\")  export EXA_DEBUG=trace ;;\n    \"status\") [ -n \"$EXA_DEBUG\" ] && echo \"debug on\" || echo \"debug off\" ;;\n    *)        echo \"Usage: debug on|off|trace|status\"; return 1 ;;\n  esac;\n}\n\n# The ‘strict’ function lets you switch strict mode on and off.\n# Turn it on if you’d like exa’s command-line arguments checked.\nstrict() {\n  case \"$1\" in\n    \"on\")  export EXA_STRICT=1 ;;\n    \"off\") export EXA_STRICT= ;;\n    \"\")    [ -n \"$EXA_STRICT\" ] && echo \"strict on\" || echo \"strict off\" ;;\n    *)     echo \"Usage: strict on|off\"; return 1 ;;\n  esac;\n}\n\n# The ‘colors’ function sets or unsets the ‘LS_COLORS’ and ‘EXA_COLORS’\n# environment variables. There’s also a ‘hacker’ theme which turns everything\n# green, which is usually used for checking that all colour codes work, and\n# for looking cool while you phreak some mainframes or whatever.\ncolors() {\n  case \"$1\" in\n    \"ls\")\n      export LS_COLORS=\"di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43\"\n      export EXA_COLORS=\"\" ;;\n    \"hacker\")\n      export LS_COLORS=\"di=32:ex=32:fi=32:pi=32:so=32:bd=32:cd=32:ln=32:or=32:mi=32\"\n      export EXA_COLORS=\"ur=32:uw=32:ux=32:ue=32:gr=32:gw=32:gx=32:tr=32:tw=32:tx=32:su=32:sf=32:xa=32:sn=32:sb=32:df=32:ds=32:uu=32:un=32:gu=32:gn=32:lc=32:lm=32:ga=32:gm=32:gd=32:gv=32:gt=32:xx=32:da=32:in=32:bl=32:hd=32:lp=32:cc=32:\" ;;\n    \"off\")\n      export LS_COLORS=\n      export EXA_COLORS= ;;\n    \"\")\n      [ -n \"$LS_COLORS\" ]  && echo \"LS_COLORS=$LS_COLORS\"   || echo \"ls-colors off\"\n      [ -n \"$EXA_COLORS\" ] && echo \"EXA_COLORS=$EXA_COLORS\" || echo \"exa-colors off\" ;;\n    *) echo \"Usage: ls-colors ls|hacker|off\"; return 1 ;;\n  esac;\n}\n"
  },
  {
    "path": "devtools/dev-create-test-filesystem.sh",
    "content": "#!/bin/bash\n# This script creates a bunch of awkward test case files. It gets\n# automatically run as part of Vagrant provisioning.\ntrap 'exit' ERR\n\nif [[ ! -d \"/vagrant\" ]]; then\n    echo \"This script should be run in the Vagrant environment\"\n    exit 1\nfi\n\nsource \"/vagrant/devtools/dev-fixtures.sh\"\n\n\n# Delete old testcases if they exist already, then create a\n# directory to house new ones.\nif [[ -d \"$TEST_ROOT\" ]]; then\n    echo -e \"\\033[1m[ 0/13]\\033[0m Deleting existing test cases directory\"\n    sudo rm -rf \"$TEST_ROOT\"\nfi\n\nsudo mkdir \"$TEST_ROOT\"\nsudo chmod 777 \"$TEST_ROOT\"\nsudo mkdir \"$TEST_ROOT/empty\"\n\n\n# Awkward file size testcases.\n# This needs sudo to set the files’ users at the very end.\nmkdir \"$TEST_ROOT/files\"\necho -e \"\\033[1m[ 1/13]\\033[0m Creating file size testcases\"\nfor i in {1..13}; do\n  fallocate -l \"$i\" \"$TEST_ROOT/files/$i\"_bytes\n  fallocate -l \"$i\"KiB \"$TEST_ROOT/files/$i\"_KiB\n  fallocate -l \"$i\"MiB \"$TEST_ROOT/files/$i\"_MiB\ndone\n\ntouch -t $FIXED_DATE \"$TEST_ROOT/files/\"*\ntouch -t $FIXED_DATE \"$TEST_ROOT/files/\"\nchmod 644 \"$TEST_ROOT/files/\"*\nsudo chown $FIXED_USER:$FIXED_USER \"$TEST_ROOT/files/\"*\n\n\n# File name extension testcases.\n# These aren’t tested in details view, but we set timestamps on them to\n# test that various sort options work.\nmkdir \"$TEST_ROOT/file-names-exts\"\necho -e \"\\033[1m[ 2/13]\\033[0m Creating file name extension testcases\"\n\ntouch \"$TEST_ROOT/file-names-exts/Makefile\"\n\ntouch \"$TEST_ROOT/file-names-exts/IMAGE.PNG\"\ntouch \"$TEST_ROOT/file-names-exts/image.svg\"\n\ntouch \"$TEST_ROOT/file-names-exts/VIDEO.AVI\"\ntouch \"$TEST_ROOT/file-names-exts/video.wmv\"\n\ntouch \"$TEST_ROOT/file-names-exts/music.mp3\"\ntouch \"$TEST_ROOT/file-names-exts/MUSIC.OGG\"\n\ntouch \"$TEST_ROOT/file-names-exts/lossless.flac\"\ntouch \"$TEST_ROOT/file-names-exts/lossless.wav\"\n\ntouch \"$TEST_ROOT/file-names-exts/crypto.asc\"\ntouch \"$TEST_ROOT/file-names-exts/crypto.signature\"\n\ntouch \"$TEST_ROOT/file-names-exts/document.pdf\"\ntouch \"$TEST_ROOT/file-names-exts/DOCUMENT.XLSX\"\n\ntouch \"$TEST_ROOT/file-names-exts/COMPRESSED.ZIP\"\ntouch \"$TEST_ROOT/file-names-exts/compressed.tar.gz\"\ntouch \"$TEST_ROOT/file-names-exts/compressed.tgz\"\ntouch \"$TEST_ROOT/file-names-exts/compressed.tar.xz\"\ntouch \"$TEST_ROOT/file-names-exts/compressed.txz\"\ntouch \"$TEST_ROOT/file-names-exts/compressed.deb\"\n\ntouch \"$TEST_ROOT/file-names-exts/backup~\"\ntouch \"$TEST_ROOT/file-names-exts/#SAVEFILE#\"\ntouch \"$TEST_ROOT/file-names-exts/file.tmp\"\n\ntouch \"$TEST_ROOT/file-names-exts/compiled.class\"\ntouch \"$TEST_ROOT/file-names-exts/compiled.o\"\ntouch \"$TEST_ROOT/file-names-exts/compiled.js\"\ntouch \"$TEST_ROOT/file-names-exts/compiled.coffee\"\n\n\n# File name testcases.\n# bash really doesn’t want you to create a file with escaped characters\n# in its name, so we have to resort to the echo builtin and touch!\nmkdir \"$TEST_ROOT/file-names\"\necho -e \"\\033[1m[ 3/13]\\033[0m Creating file names testcases\"\n\necho -ne \"$TEST_ROOT/file-names/ascii: hello\" | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/emoji: [🆒]\"  | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/utf-8: pâté\"  | xargs -0 touch\n\necho -ne \"$TEST_ROOT/file-names/bell: [\\a]\"         | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/backspace: [\\b]\"    | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/form-feed: [\\f]\"    | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/new-line: [\\n]\"     | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/return: [\\r]\"       | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/tab: [\\t]\"          | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/vertical-tab: [\\v]\" | xargs -0 touch\n\necho -ne \"$TEST_ROOT/file-names/escape: [\\033]\"               | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/ansi: [\\033[34mblue\\033[0m]\" | xargs -0 touch\n\necho -ne \"$TEST_ROOT/file-names/invalid-utf8-1: [\\xFF]\"                | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/invalid-utf8-2: [\\xc3\\x28]\"           | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/invalid-utf8-3: [\\xe2\\x82\\x28]\"      | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/invalid-utf8-4: [\\xf0\\x28\\x8c\\x28]\" | xargs -0 touch\n\necho -ne \"$TEST_ROOT/file-names/new-line-dir: [\\n]\"                | xargs -0 mkdir\necho -ne \"$TEST_ROOT/file-names/new-line-dir: [\\n]/subfile\"        | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/new-line-dir: [\\n]/another: [\\n]\" | xargs -0 touch\necho -ne \"$TEST_ROOT/file-names/new-line-dir: [\\n]/broken\"         | xargs -0 touch\n\nmkdir \"$TEST_ROOT/file-names/links\"\nln -s \"$TEST_ROOT/file-names/new-line-dir\"*/* \"$TEST_ROOT/file-names/links\"\n\necho -ne \"$TEST_ROOT/file-names/new-line-dir: [\\n]/broken\" | xargs -0 rm\n\n\n# Special file testcases.\nmkdir \"$TEST_ROOT/specials\"\necho -e \"\\033[1m[ 4/13]\\033[0m Creating special file kind testcases\"\n\nsudo mknod \"$TEST_ROOT/specials/block-device\" b  3 60\nsudo mknod \"$TEST_ROOT/specials/char-device\"  c 14 40\nsudo mknod \"$TEST_ROOT/specials/named-pipe\"   p\n\nsudo touch -t $FIXED_DATE \"$TEST_ROOT/specials/\"*\n\n\n# Awkward symlink testcases.\nmkdir \"$TEST_ROOT/links\"\necho -e \"\\033[1m[ 5/13]\\033[0m Creating symlink testcases\"\n\nln -s /            \"$TEST_ROOT/links/root\"\nln -s /usr         \"$TEST_ROOT/links/usr\"\nln -s nowhere      \"$TEST_ROOT/links/broken\"\nln -s /proc/1/root \"$TEST_ROOT/links/forbidden\"\n\ntouch \"$TEST_ROOT/links/some_file\"\nln -s \"$TEST_ROOT/links/some_file\" \"$TEST_ROOT/links/some_file_absolute\"\n(cd \"$TEST_ROOT/links\"; ln -s \"some_file\" \"some_file_relative\")\n(cd \"$TEST_ROOT/links\"; ln -s \".\"         \"current_dir\")\n(cd \"$TEST_ROOT/links\"; ln -s \"..\"        \"parent_dir\")\n(cd \"$TEST_ROOT/links\"; ln -s \"itself\"    \"itself\")\n\n\n# Awkward passwd testcases.\n# sudo is needed for these because we technically aren’t a member\n# of the groups (because they don’t exist), and chown and chgrp\n# are smart enough to disallow it!\nmkdir \"$TEST_ROOT/passwd\"\necho -e \"\\033[1m[ 6/13]\\033[0m Creating user and group testcases\"\n\ntouch -t $FIXED_DATE                  \"$TEST_ROOT/passwd/unknown-uid\"\nchmod 644                             \"$TEST_ROOT/passwd/unknown-uid\"\nsudo chown $FIXED_BAD_UID:$FIXED_USER \"$TEST_ROOT/passwd/unknown-uid\"\n\ntouch -t $FIXED_DATE                  \"$TEST_ROOT/passwd/unknown-gid\"\nchmod 644                             \"$TEST_ROOT/passwd/unknown-gid\"\nsudo chown $FIXED_USER:$FIXED_BAD_GID \"$TEST_ROOT/passwd/unknown-gid\"\n\n\n# Awkward permission testcases.\n# Differences in the way ‘chmod’ handles setting ‘setuid’ and ‘setgid’\n# when you don’t already own the file mean that we need to use ‘sudo’\n# to change permissions to those.\nmkdir \"$TEST_ROOT/permissions\"\necho -e \"\\033[1m[ 7/13]\\033[0m Creating file permission testcases\"\n\nmkdir                              \"$TEST_ROOT/permissions/forbidden-directory\"\nchmod 000                          \"$TEST_ROOT/permissions/forbidden-directory\"\ntouch -t $FIXED_DATE               \"$TEST_ROOT/permissions/forbidden-directory\"\nsudo chown $FIXED_USER:$FIXED_USER \"$TEST_ROOT/permissions/forbidden-directory\"\n\nfor perms in 000 001 002 004 010 020 040 100 200 400 644 755 777 1000 1001 2000 2010 4000 4100 7666 7777; do\n    touch                              \"$TEST_ROOT/permissions/$perms\"\n    sudo chown $FIXED_USER:$FIXED_USER \"$TEST_ROOT/permissions/$perms\"\n    sudo chmod $perms                  \"$TEST_ROOT/permissions/$perms\"\n    sudo touch -t $FIXED_DATE          \"$TEST_ROOT/permissions/$perms\"\ndone\n\n\n# Awkward date and time testcases.\nmkdir \"$TEST_ROOT/dates\"\necho -e \"\\033[1m[ 8/13]\\033[0m Creating date and time testcases\"\n\n# created dates\n# there’s no way to touch the created date of a file...\n# so we have to do this the old-fashioned way!\n# (and make sure these don't actually get listed)\ntouch -t $FIXED_OLD_DATE    \"$TEST_ROOT/dates/peach\";  sleep 1\ntouch -t $FIXED_MED_DATE    \"$TEST_ROOT/dates/plum\";   sleep 1\ntouch -t $FIXED_NEW_DATE    \"$TEST_ROOT/dates/pear\"\n\n# modified dates\ntouch -t $FIXED_OLD_DATE -m \"$TEST_ROOT/dates/pear\"\ntouch -t $FIXED_MED_DATE -m \"$TEST_ROOT/dates/peach\"\ntouch -t $FIXED_NEW_DATE -m \"$TEST_ROOT/dates/plum\"\n\n# accessed dates\ntouch -t $FIXED_OLD_DATE -a \"$TEST_ROOT/dates/plum\"\ntouch -t $FIXED_MED_DATE -a \"$TEST_ROOT/dates/pear\"\ntouch -t $FIXED_NEW_DATE -a \"$TEST_ROOT/dates/peach\"\n\nsudo chown $FIXED_USER:$FIXED_USER -R \"$TEST_ROOT/dates\"\n\nmkdir \"$TEST_ROOT/far-dates\"\ntouch -t $FIXED_PAST_DATE    \"$TEST_ROOT/far-dates/the-distant-past\"\ntouch -t $FIXED_FUTURE_DATE  \"$TEST_ROOT/far-dates/beyond-the-future\"\n\n\n# Awkward extended attribute testcases.\n# We need to test combinations of various numbers of files *and*\n# extended attributes in directories. Turns out, the easiest way to\n# do this is to generate all combinations of files with “one-xattr”\n# or “two-xattrs” in their name and directories with “empty” or\n# “one-file” in their name, then just give the right number of\n# xattrs and children to those.\nmkdir \"$TEST_ROOT/attributes\"\necho -e \"\\033[1m[ 9/13]\\033[0m Creating extended attribute testcases\"\n\nmkdir \"$TEST_ROOT/attributes/files\"\ntouch \"$TEST_ROOT/attributes/files/\"{no-xattrs,one-xattr,two-xattrs}{,_forbidden}\n\nmkdir \"$TEST_ROOT/attributes/dirs\"\nmkdir \"$TEST_ROOT/attributes/dirs/\"{no-xattrs,one-xattr,two-xattrs}_{empty,one-file,two-files}{,_forbidden}\n\nsetfattr -n user.greeting         -v hello \"$TEST_ROOT/attributes\"/**/*{one-xattr,two-xattrs}*\nsetfattr -n user.another_greeting -v hi    \"$TEST_ROOT/attributes\"/**/*two-xattrs*\n\nfor dir in \"$TEST_ROOT/attributes/dirs/\"*one-file*; do\n    touch $dir/file-in-question\ndone\n\nfor dir in \"$TEST_ROOT/attributes/dirs/\"*two-files*; do\n    touch $dir/this-file\n    touch $dir/that-file\ndone\n\nfind \"$TEST_ROOT/attributes\" -exec touch {} -t $FIXED_DATE \\;\n\n# I want to use the following to test,\n# but it only works on macos:\n#chmod +a \"$FIXED_USER deny readextattr\" \"$TEST_ROOT/attributes\"/**/*_forbidden\n\nsudo chmod 000                        \"$TEST_ROOT/attributes\"/**/*_forbidden\nsudo chown $FIXED_USER:$FIXED_USER -R \"$TEST_ROOT/attributes\"\n\n\n# A sample Git repository\n# This uses cd because it's easier than telling Git where to go each time\necho -e \"\\033[1m[10/13]\\033[0m Creating Git testcases (1/4)\"\nmkdir \"$TEST_ROOT/git\"\ncd    \"$TEST_ROOT/git\"\ngit init >/dev/null\n\nmkdir edits additions moves\n\necho \"original content\" | tee edits/{staged,unstaged,both} >/dev/null\necho \"this file gets moved\" > moves/hither\n\ngit add edits moves\ngit config --global user.email \"exa@exa.exa\"\ngit config --global user.name \"Exa Exa\"\ngit commit -m \"Automated test commit\" >/dev/null\n\necho \"modifications!\" | tee edits/{staged,both} >/dev/null\ntouch additions/{staged,edited}\nmv moves/{hither,thither}\n\ngit add edits moves additions\necho \"more modifications!\" | tee edits/unstaged edits/both additions/edited >/dev/null\ntouch additions/unstaged\n\nfind \"$TEST_ROOT/git\" -exec touch {} -t $FIXED_DATE \\;\nsudo chown $FIXED_USER:$FIXED_USER -R \"$TEST_ROOT/git\"\n\n\n# A second Git repository\n# for testing two at once\necho -e \"\\033[1m[11/13]\\033[0m Creating Git testcases (2/4)\"\nmkdir -p \"$TEST_ROOT/git2/deeply/nested/directory\"\ncd       \"$TEST_ROOT/git2\"\ngit init >/dev/null\n\ntouch \"deeply/nested/directory/upd8d\"\ngit add \"deeply/nested/directory/upd8d\"\ngit commit -m \"Automated test commit\" >/dev/null\n\necho \"Now with contents\" > \"deeply/nested/directory/upd8d\"\ntouch \"deeply/nested/directory/l8st\"\n\necho -e \"target\\n*.mp3\" > \".gitignore\"\nmkdir \"ignoreds\"\ntouch \"ignoreds/music.mp3\"\ntouch \"ignoreds/music.m4a\"\nmkdir \"ignoreds/nested\"\ntouch \"ignoreds/nested/70s grove.mp3\"\ntouch \"ignoreds/nested/funky chicken.m4a\"\nmkdir \"ignoreds/nested2\"\ntouch \"ignoreds/nested2/ievan polkka.mp3\"\n\nmkdir \"target\"\ntouch \"target/another ignored file\"\n\nmkdir \"deeply/nested/repository\"\ncd    \"deeply/nested/repository\"\ngit init >/dev/null\ntouch subfile\n# This file, ‘subfile’, should _not_ be marked as a new file by exa, because\n# it’s in the sub-repository but hasn’t been added to it. Were the sub-repo not\n# present, it would be marked as a new file, as the top-level repo knows about\n# the ‘deeply’ directory.\n\nfind \"$TEST_ROOT/git2\" -exec touch {} -t $FIXED_DATE \\;\nsudo chown $FIXED_USER:$FIXED_USER -R \"$TEST_ROOT/git2\"\n\n\n# A third Git repository\n# Regression test for https://github.com/ogham/exa/issues/526\necho -e \"\\033[1m[12/13]\\033[0m Creating Git testcases (3/4)\"\nmkdir -p \"$TEST_ROOT/git3\"\ncd       \"$TEST_ROOT/git3\"\ngit init >/dev/null\n\n# Create a symbolic link pointing to a non-existing file\nln -s aaa/aaa/a b\n\n# This normally fails with:\nfind \"$TEST_ROOT/git3\" -exec touch {} -h -t $FIXED_DATE \\;\nsudo chown $FIXED_USER:$FIXED_USER -R \"$TEST_ROOT/git3\"\n\n\n# A fourth Git repository\n# Regression test for https://github.com/ogham/exa/issues/698\necho -e \"\\033[1m[12/13]\\033[0m Creating Git testcases (4/4)\"\nmkdir -p \"$TEST_ROOT/git4\"\ncd       \"$TEST_ROOT/git4\"\ngit init >/dev/null\n\n# Create a non UTF-8 file\ntouch 'P'$'\\b\\211''UUU'\n\nfind \"$TEST_ROOT/git4\" -exec touch {} -h -t $FIXED_DATE \\;\nsudo chown $FIXED_USER:$FIXED_USER -R \"$TEST_ROOT/git4\"\n\n\n# Hidden and dot file testcases.\n# We need to set the permissions of `.` and `..` because they actually\n# get displayed in the output here, so this has to come last.\necho -e \"\\033[1m[13/13]\\033[0m Creating hidden and dot file testcases\"\nshopt -u dotglob\nGLOBIGNORE=\".:..\"\n\nmkdir \"$TEST_ROOT/hiddens\"\ncd    \"$TEST_ROOT/hiddens\"\ntouch \"$TEST_ROOT/hiddens/visible\"\ntouch \"$TEST_ROOT/hiddens/.hidden\"\ntouch \"$TEST_ROOT/hiddens/..extra-hidden\"\n\n# ./hiddens/\ntouch -t $FIXED_DATE               \"$TEST_ROOT/hiddens/\"*\nchmod 644                          \"$TEST_ROOT/hiddens/\"*\nsudo chown $FIXED_USER:$FIXED_USER \"$TEST_ROOT/hiddens/\"*\n\n# .\ntouch -t $FIXED_DATE               \"$TEST_ROOT/hiddens\"\nchmod 755                          \"$TEST_ROOT/hiddens\"\nsudo chown $FIXED_USER:$FIXED_USER \"$TEST_ROOT/hiddens\"\n\n# ..\nsudo touch -t $FIXED_DATE          \"$TEST_ROOT\"\nsudo chmod 755                     \"$TEST_ROOT\"\nsudo chown $FIXED_USER:$FIXED_USER \"$TEST_ROOT\"\n"
  },
  {
    "path": "devtools/dev-fixtures.sh",
    "content": "#!/bin/bash\n# This file contains the text fixtures — the known, constant data — that are\n# used when setting up the environment that exa’s tests get run in.\n\n\n# The directory that all the test files are created under.\nexport TEST_ROOT=/testcases\n\n\n# Because the timestamps are formatted differently depending on whether\n# they’re in the current year or not (see `details.rs`), we have to make\n# sure that the files are created in the current year, so they get shown\n# in the format we expect.\nexport CURRENT_YEAR=$(date \"+%Y\")\nexport FIXED_DATE=\"${CURRENT_YEAR}01011234.56\"  # 1st January, 12:34:56\n\n\n# We also need an UID and a GID that are guaranteed to not exist, to\n# test what happen when they don’t.\nexport FIXED_BAD_UID=666\nexport FIXED_BAD_GID=616\n\n\n# We create two users that own the test files.\n#\n# The first one just owns the ordinary ones, because we don’t want the\n# test outputs to depend on “vagrant” or “ubuntu” existing.\n#\n# The second one has a long name, to test that the file owner column\n# widens correctly. The benefit of Vagrant is that we don’t need to\n# set this up on the *actual* system!\nexport FIXED_USER=\"cassowary\"\nexport FIXED_LONG_USER=\"antidisestablishmentarienism\"\n\n\n# A couple of dates, for date-time testing.\nexport FIXED_OLD_DATE='200303030000.00'\nexport FIXED_MED_DATE='200606152314.29'   # the june gets used for fr_FR locale tests\nexport FIXED_NEW_DATE='200912221038.53'   # and the december for ja_JP local tests\n\n# Dates that extend beyond 32-bit timespace.\nexport FIXED_PAST_DATE='170001010000.00'\nexport FIXED_FUTURE_DATE='230001010000.00'\n"
  },
  {
    "path": "devtools/dev-help.sh",
    "content": "# This file prints out some help text that says which commands are available\n# in the VM. It gets executed during Vagrant provisioning and its output gets\n# dumped into /etc/motd, to print it when a user starts a new Vagrant session.\n\n\necho -e \"\n\\033[1;33mThe exa development environment!\\033[0m\nexa's source is available at \\033[33m/vagrant\\033[0m.\nBinaries get built into \\033[33m/home/vagrant/target\\033[0m.\n\n\\033[4mCommands\\033[0m\n\\033[32;1mexa\\033[0m to run the built version of exa\n\\033[32;1mbuild-exa\\033[0m (or \\033[32;1mb\\033[0m) to run \\033[1mcargo build\\033[0m\n\\033[32;1mtest-exa\\033[0m (or \\033[32;1mt\\033[0m) to run \\033[1mcargo test\\033[0m\n\\033[32;1mrun-xtests\\033[0m (or \\033[32;1mx\\033[0m) to run the extended tests\n\\033[32;1mcompile-exa\\033[0m (or \\033[32;1mc\\033[0m) to run the above three\n\\033[32;1mdebug\\033[0m to toggle printing logs\n\\033[32;1mstrict\\033[0m to toggle strict mode\n\\033[32;1mcolors\\033[0m to toggle custom colours\n\\033[32;1mhalp\\033[0m to show all this again\n\"\n"
  },
  {
    "path": "devtools/dev-package-for-linux.sh",
    "content": "set -e\n\n# This script builds a publishable release-worthy version of exa.\n# It gets the version number, builds exa using cargo, tests it, strips the\n# binary, compresses it into a zip, then puts it in /vagrant so it’s\n# accessible from the host machine.\n#\n# If you’re in the VM, you can run it using the ‘package-exa’ command.\n\n\n# Linux check!\nuname=$(uname -s)\nif [[ \"$uname\" != \"Linux\" ]]; then\n  echo \"Gotta be on Linux to run this (detected '$uname')!\"\n  exit 1\nfi\n\n# First, we need to get the version number to figure out what to call the zip.\n# We do this by getting the first line from the Cargo.toml that matches\n# /version/, removing its whitespace, and building a command out of it, so the\n# shell executes something like `exa_version=\"0.8.0\"`, which it understands as\n# a variable definition. Hey, it’s not a hack if it works.\ntoml_file=\"/vagrant/Cargo.toml\"\neval exa_$(grep version $toml_file | head -n 1 | sed \"s/ //g\")\nif [ -z \"$exa_version\" ]; then\n  echo \"Failed to parse version number! Can't build exa!\"\n  exit 1\nfi\n\n# Weekly builds have a bit more information in their version number (see build.rs).\nif [[ \"$1\" == \"--weekly\" ]]; then\n  git_hash=$(GIT_DIR=/vagrant/.git git rev-parse --short --verify HEAD)\n  date=$(date +\"%Y-%m-%d\")\n  echo \"Building exa weekly v$exa_version, date $date, Git hash $git_hash\"\nelse\n  echo \"Building exa v$exa_version\"\nfi\n\n# Compilation is done in --release mode, which takes longer but produces a\n# faster binary. This binary gets built to a different place, so the extended\n# tests script needs to be told which one to use.\necho -e \"\\n\\033[4mCompiling release version of exa...\\033[0m\"\nexa_linux_binary=\"/vagrant/exa-linux-x86_64\"\nrm -vf \"$exa_linux_binary\"\ncargo build --release --manifest-path \"$toml_file\"\ncargo test --release --manifest-path \"$toml_file\" --lib -- --quiet\n/vagrant/xtests/run.sh --release\ncp /home/vagrant/target/release/exa \"$exa_linux_binary\"\n\n# Stripping the binary before distributing it removes a bunch of debugging\n# symbols, saving some space.\necho -e \"\\n\\033[4mStripping binary...\\033[0m\"\nstrip -v \"$exa_linux_binary\"\n\n# Compress the binary for upload. The ‘-j’ flag is necessary to avoid the\n# /vagrant path being in the zip too. Only the zip gets the version number, so\n# the binaries can have consistent names, and it’s still possible to tell\n# different *downloads* apart.\necho -e \"\\n\\033[4mZipping binary...\\033[0m\"\nif [[ \"$1\" == \"--weekly\" ]]; then\n  exa_linux_zip=\"/vagrant/exa-linux-x86_64-${exa_version}-${date}-${git_hash}.zip\"\nelse\n  exa_linux_zip=\"/vagrant/exa-linux-x86_64.zip\"\nfi\nrm -vf \"$exa_linux_zip\"\nzip -j \"$exa_linux_zip\" \"$exa_linux_binary\"\n\n# There was a problem a while back where a library was getting unknowingly\n# *dynamically* linked, which broke the whole ‘self-contained binary’ concept.\n# So dump the linker table, in case anything unscrupulous shows up.\necho -e \"\\n\\033[4mLibraries linked:\\033[0m\"\nldd \"$exa_linux_binary\" | sed \"s/\\t//\"\n\n# Might as well use it to test itself, right?\necho -e \"\\n\\033[4mAll done! Files produced:\\033[0m\"\n\"$exa_linux_binary\" \"$exa_linux_binary\" \"$exa_linux_zip\" -lB\n"
  },
  {
    "path": "devtools/dev-run-debug.sh",
    "content": "#!/bin/bash\nif [[ -f ~/target/debug/exa ]]; then\n  ~/target/debug/exa \"$@\"\nelse\n  echo -e \"Debug exa binary does not exist!\"\n  echo -e \"Run \\033[32;1mb\\033[0m or \\033[32;1mbuild-exa\\033[0m to create it\"\nfi\n"
  },
  {
    "path": "devtools/dev-run-release.sh",
    "content": "#!/bin/bash\nif [[ -f ~/target/release/exa ]]; then\n  ~/target/release/exa \"$@\"\nelse\n  echo -e \"Release exa binary does not exist!\"\n  echo -e \"Run \\033[32;1mb --release\\033[0m or \\033[32;1mbuild-exa --release\\033[0m to create it\"\nfi\n"
  },
  {
    "path": "devtools/dev-set-up-environment.sh",
    "content": "#!/bin/bash\n\nif [[ ! -d \"/vagrant\" ]]; then\n    echo \"This script should be run in the Vagrant environment\"\n    exit 1\nfi\n\nif [[ $EUID -ne 0 ]]; then\n    echo \"This script should be run as root\"\n    exit 1\nfi\n\nsource \"/vagrant/devtools/dev-fixtures.sh\"\n\n\n# create our test users\n\nif id -u $FIXED_USER &>/dev/null; then\n    echo \"Normal user already exists\"\nelse\n    echo \"Creating normal user\"\n    useradd $FIXED_USER\nfi\n\nif id -u $FIXED_LONG_USER &>/dev/null; then\n    echo \"Long user already exists\"\nelse\n    echo \"Creating long user\"\n    useradd $FIXED_LONG_USER\nfi\n\n\n# locale generation\n\n# remove most of this file, it slows down locale-gen\nif grep -F -q \"en_GB.UTF-8 UTF-8\" /var/lib/locales/supported.d/en; then\n    echo \"Removing existing locales\"\n    echo \"en_US.UTF-8 UTF-8\" > /var/lib/locales/supported.d/en\nfi\n\n# uncomment these from the config file\nif grep -F -q \"# fr_FR.UTF-8\" /etc/locale.gen; then\n    sed -i '/fr_FR.UTF-8/s/^# //g' /etc/locale.gen\nfi\nif grep -F -q \"# ja_JP.UTF-8\" /etc/locale.gen; then\n    sed -i '/ja_JP.UTF-8/s/^# //g' /etc/locale.gen\nfi\n\n# only regenerate locales if the config files are newer than the locale archive\nif [[ ( /var/lib/locales/supported.d/en -nt /usr/lib/locale/locale-archive ) || \\\n      ( /etc/locale_gen                 -nt /usr/lib/locale/locale-archive ) ]]; then\n    locale-gen\nelse\n    echo \"Locales already generated\"\nfi\n"
  },
  {
    "path": "devtools/dev-versions.sh",
    "content": "# Displays the installed versions of Rust and Cargo.\n# This gets run from ‘dev-bash.sh’, which gets run from ‘~/.bash_profile’, so\n# the versions gets displayed after the help text for a new Vagrant session.\n\necho -e \"\\\\033[4mVersions\\\\033[0m\"\nrustc --version\ncargo --version\necho\n"
  },
  {
    "path": "devtools/local-package-for-macos.sh",
    "content": "set -e\n\n# This script builds a publishable release-worthy version of exa.\n# It gets the version number, builds exa using cargo, tests it, strips the\n# binary, and compresses it into a zip.\n#\n# It’s *mostly* the same as dev-package-for-linux.sh, except with some\n# Mach-specific things (otool instead of ldd), BSD-coreutils-specific things,\n# and it doesn’t run the xtests.\n\n\n# Virtualising macOS is a legal minefield, so this script is ‘local’ instead\n# of ‘dev’: I run it from my actual machine, rather than from a VM.\nuname=$(uname -s)\nif [[ \"$uname\" != \"Darwin\" ]]; then\n  echo \"Gotta be on Darwin to run this (detected '$uname')!\"\n  exit 1\nfi\n\n# First, we need to get the version number to figure out what to call the zip.\n# We do this by getting the first line from the Cargo.toml that matches\n# /version/, removing its whitespace, and building a command out of it, so the\n# shell executes something like `exa_version=\"0.8.0\"`, which it understands as\n# a variable definition. Hey, it’s not a hack if it works.\n#\n# Because this can’t use the absolute /vagrant path, this has to use what this\n# SO answer calls a “quoting disaster”: https://stackoverflow.com/a/20196098/3484614\n# You will also need GNU coreutils: https://stackoverflow.com/a/4031502/3484614\nexa_root=\"$(dirname \"$(dirname \"$(greadlink -fm \"$0\")\")\")\"\ntoml_file=\"$exa_root\"/Cargo.toml\neval exa_$(grep version $toml_file | head -n 1 | sed \"s/ //g\")\nif [ -z \"$exa_version\" ]; then\n  echo \"Failed to parse version number! Can't build exa!\"\n  exit 1\nfi\n\n# Weekly builds have a bit more information in their version number (see build.rs).\nif [[ \"$1\" == \"--weekly\" ]]; then\n  git_hash=$(GIT_DIR=$exa_root/.git git rev-parse --short --verify HEAD)\n  date=$(date +\"%Y-%m-%d\")\n  echo \"Building exa weekly v$exa_version, date $date, Git hash $git_hash\"\nelse\n  echo \"Building exa v$exa_version\"\nfi\n\n# Compilation is done in --release mode, which takes longer but produces a\n# faster binary.\necho -e \"\\n\\033[4mCompiling release version of exa...\\033[0m\"\nexa_macos_binary=\"$exa_root/exa-macos-x86_64\"\nrm -vf \"$exa_macos_binary\" | sed 's/^/removing /'\ncargo build --release --manifest-path \"$toml_file\"\ncargo test --release --manifest-path \"$toml_file\" --lib -- --quiet\n# we can’t run the xtests outside the VM!\n#/vagrant/xtests/run.sh --release\ncp \"$exa_root\"/target/release/exa \"$exa_macos_binary\"\n\n# Stripping the binary before distributing it removes a bunch of debugging\n# symbols, saving some space.\necho -e \"\\n\\033[4mStripping binary...\\033[0m\"\nstrip \"$exa_macos_binary\"\necho \"strip $exa_macos_binary\"\n\n# Compress the binary for upload. The ‘-j’ flag is necessary to avoid the\n# current path being in the zip too. Only the zip gets the version number, so\n# the binaries can have consistent names, and it’s still possible to tell\n# different *downloads* apart.\necho -e \"\\n\\033[4mZipping binary...\\033[0m\"\nif [[ \"$1\" == \"--weekly\" ]]; then\n  exa_macos_zip=\"$exa_root/exa-macos-x86_64-${exa_version}-${date}-${git_hash}.zip\"\nelse\n  exa_macos_zip=\"$exa_root/exa-macos-x86_64-${exa_version}.zip\"\nfi\nrm -vf \"$exa_macos_zip\" | sed 's/^/removing /'\nzip -j \"$exa_macos_zip\" \"$exa_macos_binary\"\n\n# There was a problem a while back where a library was getting unknowingly\n# *dynamically* linked, which broke the whole ‘self-contained binary’ concept.\n# So dump the linker table, in case anything unscrupulous shows up.\necho -e \"\\n\\033[4mLibraries linked:\\033[0m\"\notool -L \"$exa_macos_binary\" | sed 's/^[[:space:]]*//'\n\n# Might as well use it to test itself, right?\necho -e \"\\n\\033[4mAll done! Files produced:\\033[0m\"\n\"$exa_macos_binary\" \"$exa_macos_binary\" \"$exa_macos_zip\" -lB\n"
  },
  {
    "path": "man/exa.1.md",
    "content": "% exa(1) v0.9.0\n\n<!-- This is the exa(1) man page, written in Markdown. -->\n<!-- To generate the roff version, run `just man`, -->\n<!-- and the man page will appear in the ‘target’ directory. -->\n\n\nNAME\n====\n\nexa — a modern replacement for ls\n\n\nSYNOPSIS\n========\n\n`exa [options] [files...]`\n\n**exa** is a modern replacement for `ls`.\nIt uses colours for information by default, helping you distinguish between many types of files, such as whether you are the owner, or in the owning group.\n\nIt also has extra features not present in the original `ls`, such as viewing the Git status for a directory, or recursing into directories with a tree view.\n\n\nEXAMPLES\n========\n\n`exa`\n: Lists the contents of the current directory in a grid.\n\n`exa --oneline --reverse --sort=size`\n: Displays a list of files with the largest at the top.\n\n`exa --long --header --inode --git`\n: Displays a table of files with a header, showing each file’s metadata, inode, and Git status.\n\n`exa --long --tree --level=3`\n: Displays a tree of files, three levels deep, as well as each file’s metadata.\n\n\nDISPLAY OPTIONS\n===============\n\n`-1`, `--oneline`\n: Display one entry per line.\n\n`-F`, `--classify`\n: Display file kind indicators next to file names.\n\n`-G`, `--grid`\n: Display entries as a grid (default).\n\n`-l`, `--long`\n: Display extended file metadata as a table.\n\n`-R`, `--recurse`\n: Recurse into directories.\n\n`-T`, `--tree`\n: Recurse into directories as a tree.\n\n`-x`, `--across`\n: Sort the grid across, rather than downwards.\n\n`--color`, `--colour=WHEN`\n: When to use terminal colours.\nValid settings are ‘`always`’, ‘`automatic`’, and ‘`never`’.\n\n`--color-scale`, `--colour-scale`\n: Colour file sizes on a scale.\n\n`--icons`\n: Display icons next to file names.\n\n`--no-icons`\n: Don't display icons. (Always overrides --icons)\n\n\nFILTERING AND SORTING OPTIONS\n=============================\n\n`-a`, `--all`\n: Show hidden and “dot” files.\nUse this twice to also show the ‘`.`’ and ‘`..`’ directories.\n\n`-d`, `--list-dirs`\n: List directories as regular files, rather than recursing and listing their contents.\n\n`-L`, `--level=DEPTH`\n: Limit the depth of recursion.\n\n`-r`, `--reverse`\n: Reverse the sort order.\n\n`-s`, `--sort=SORT_FIELD`\n: Which field to sort by.\n\nValid sort fields are ‘`name`’, ‘`Name`’, ‘`extension`’, ‘`Extension`’, ‘`size`’, ‘`modified`’, ‘`changed`’, ‘`accessed`’, ‘`created`’, ‘`inode`’, ‘`type`’, and ‘`none`’.\n\nThe `modified` sort field has the aliases ‘`date`’, ‘`time`’, and ‘`newest`’, and its reverse order has the aliases ‘`age`’ and ‘`oldest`’.\n\nSort fields starting with a capital letter will sort uppercase before lowercase: ‘A’ then ‘B’ then ‘a’ then ‘b’. Fields starting with a lowercase letter will mix them: ‘A’ then ‘a’ then ‘B’ then ‘b’.\n\n`-I`, `--ignore-glob=GLOBS`\n: Glob patterns, pipe-separated, of files to ignore.\n\n`--git-ignore` [if exa was built with git support]\n: Do not list files that are ignored by Git.\n\n`--group-directories-first`\n: List directories before other files.\n\n`-D`, `--only-dirs`\n: List only directories, not files.\n\n\nLONG VIEW OPTIONS\n=================\n\nThese options are available when running with `--long` (`-l`):\n\n`-b`, `--binary`\n: List file sizes with binary prefixes.\n\n`-B`, `--bytes`\n: List file sizes in bytes, without any prefixes.\n\n`--changed`\n: Use the changed timestamp field.\n\n`-g`, `--group`\n: List each file’s group.\n\n`-h`, `--header`\n: Add a header row to each column.\n\n`-H`, `--links`\n: List each file’s number of hard links.\n\n`-i`, `--inode`\n: List each file’s inode number.\n\n`-m`, `--modified`\n: Use the modified timestamp field.\n\n`-n`, `--numeric`\n: List numeric user and group IDs.\n\n`-S`, `--blocks`\n: List each file’s number of file system blocks.\n\n`-t`, `--time=WORD`\n: Which timestamp field to list.\n\n: Valid timestamp fields are ‘`modified`’, ‘`changed`’, ‘`accessed`’, and ‘`created`’.\n\n`--time-style=STYLE`\n: How to format timestamps.\n\n: Valid timestamp styles are ‘`default`’, ‘`iso`’, ‘`long-iso`’, and ‘`full-iso`’.\n\n`-u`, `--accessed`\n: Use the accessed timestamp field.\n\n`-U`, `--created`\n: Use the created timestamp field.\n\n`--no-permissions`\n: Suppress the permissions field.\n\n`--no-filesize`\n: Suppress the file size field.\n\n`--no-user`\n: Suppress the user field.\n\n`--no-time`\n: Suppress the time field.\n\n`-@`, `--extended`\n: List each file’s extended attributes and sizes.\n\n`--git`  [if exa was built with git support]\n: List each file’s Git status, if tracked.\n\nThis adds a two-character column indicating the staged and unstaged statuses respectively. The status character can be ‘`-`’ for not modified, ‘`M`’ for a modified file, ‘`N`’ for a new file, ‘`D`’ for deleted, ‘`R`’ for renamed, ‘`T`’ for type-change, ‘`I`’ for ignored, and ‘`U`’ for conflicted.\n\nDirectories will be shown to have the status of their contents, which is how ‘deleted’ is possible: if a directory contains a file that has a certain status, it will be shown to have that status.\n\n\nENVIRONMENT VARIABLES\n=====================\n\nexa responds to the following environment variables:\n\n## `COLUMNS`\n\nOverrides the width of the terminal, in characters.\n\nFor example, ‘`COLUMNS=80 exa`’ will show a grid view with a maximum width of 80 characters.\n\nThis option won’t do anything when exa’s output doesn’t wrap, such as when using the `--long` view.\n\n## `EXA_STRICT`\n\nEnables _strict mode_, which will make exa error when two command-line options are incompatible.\n\nUsually, options can override each other going right-to-left on the command line, so that exa can be given aliases: creating an alias ‘`exa=exa --sort=ext`’ then running ‘`exa --sort=size`’ with that alias will run ‘`exa --sort=ext --sort=size`’, and the sorting specified by the user will override the sorting specified by the alias.\n\nIn strict mode, the two options will not co-operate, and exa will error.\n\nThis option is intended for use with automated scripts and other situations where you want to be certain you’re typing in the right command.\n\n## `EXA_GRID_ROWS`\n\nLimits the grid-details view (‘`exa --grid --long`’) so it’s only activated when at least the given number of rows of output would be generated.\n\nWith widescreen displays, it’s possible for the grid to look very wide and sparse, on just one or two lines with none of the columns lining up.\nBy specifying a minimum number of rows, you can only use the view if it’s going to be worth using.\n\n## `EXA_ICON_SPACING`\n\nSpecifies the number of spaces to print between an icon (see the ‘`--icons`’ option) and its file name.\n\nDifferent terminals display icons differently, as they usually take up more than one character width on screen, so there’s no “standard” number of spaces that exa can use to separate an icon from text. One space may place the icon too close to the text, and two spaces may place it too far away. So the choice is left up to the user to configure depending on their terminal emulator.\n\n## `NO_COLOR`\n\nDisables colours in the output (regardless of its value). Can be overridden by `--color` option.\n\nSee `https://no-color.org/` for details.\n\n## `LS_COLORS`, `EXA_COLORS`\n\nSpecifies the colour scheme used to highlight files based on their name and kind, as well as highlighting metadata and parts of the UI.\n\nFor more information on the format of these environment variables, see the `exa_colors(5)` manual page.\n\n\nEXIT STATUSES\n=============\n\n0\n: If everything goes OK.\n\n1\n: If there was an I/O error during operation.\n\n3\n: If there was a problem with the command-line arguments.\n\n\nAUTHOR\n======\n\nexa is maintained by Benjamin ‘ogham’ Sago and many other contributors.\n\n**Website:** `https://the.exa.website/` \\\n**Source code:** `https://github.com/ogham/exa` \\\n**Contributors:** `https://github.com/ogham/exa/graphs/contributors`\n\n\nSEE ALSO\n========\n\n- `exa_colors(5)`\n"
  },
  {
    "path": "man/exa_colors.5.md",
    "content": "% exa_colors(5) v0.9.0\n\n<!-- This is the exa_colors(5) man page, written in Markdown. -->\n<!-- To generate the roff version, run `just man`, -->\n<!-- and the man page will appear in the ‘target’ directory. -->\n\n\nNAME\n====\n\nexa_colors — customising the file and UI colours of exa\n\n\nSYNOPSIS\n========\n\nThe `EXA_COLORS` environment variable can be used to customise the colours that `exa` uses to highlight file names, file metadata, and parts of the UI.\n\nYou can use the `dircolors` program to generate a script that sets the variable from an input file, or if you don’t mind editing long strings of text, you can just type it out directly. These variables have the following structure:\n\n- A list of key-value pairs separated by ‘`=`’, such as ‘`*.txt=32`’.\n- Multiple ANSI formatting codes are separated by ‘`;`’, such as ‘`*.txt=32;1;4`’.\n- Finally, multiple pairs are separated by ‘`:`’, such as ‘`*.txt=32:*.mp3=1;35`’.\n\nThe key half of the pair can either be a two-letter code or a file glob, and anything that’s not a valid code will be treated as a glob, including keys that happen to be two letters long.\n\n\nEXAMPLES\n========\n\n`EXA_COLORS=\"uu=0:gu=0\"`\n: Disable the “current user” highlighting\n\n`EXA_COLORS=\"da=32\"`\n: Turn the date column green\n\n`EXA_COLORS=\"Vagrantfile=1;4;33\"`\n: Highlight Vagrantfiles\n\n`EXA_COLORS=\"*.zip=38;5;125\"`\n: Override the existing zip colour\n\n`EXA_COLORS=\"*.md=38;5;121:*.log=38;5;248\"`\n: Markdown files a shade of green, log files a shade of grey\n\n\nLIST OF CODES\n=============\n\n`LS_COLORS` can use these ten codes:\n\n`di`\n: directories\n\n`ex`\n: executable files\n\n`fi`\n: regular files\n\n`pi`\n: named pipes\n\n`so`\n: sockets\n\n`bd`\n: block devices\n\n`cd`\n: character devices\n\n`ln`\n: symlinks\n\n`or`\n: symlinks with no target\n\n\n`EXA_COLORS` can use many more:\n\n`ur`\n: the user-read permission bit\n\n`uw`\n: the user-write permission bit\n\n`ux`\n: the user-execute permission bit for regular files\n\n`ue`\n: the user-execute for other file kinds\n\n`gr`\n: the group-read permission bit\n\n`gw`\n: the group-write permission bit\n\n`gx`\n: the group-execute permission bit\n\n`tr`\n: the others-read permission bit\n\n`tw`\n: the others-write permission bit\n\n`tx`\n: the others-execute permission bit\n\n`su`\n: setuid, setgid, and sticky permission bits for files\n\n`sf`\n: setuid, setgid, and sticky for other file kinds\n\n`xa`\n: the extended attribute indicator\n\n`sn`\n: the numbers of a file’s size (sets `nb`, `nk`, `nm`, `ng` and `nh`)\n\n`nb`\n: the numbers of a file’s size if it is lower than 1 KB/Kib\n\n`nk`\n: the numbers of a file’s size if it is between 1 KB/KiB and 1 MB/MiB\n\n`nm`\n: the numbers of a file’s size if it is between 1 MB/MiB and 1 GB/GiB\n\n`ng`\n: the numbers of a file’s size if it is between 1 GB/GiB and 1 TB/TiB\n\n`nt`\n: the numbers of a file’s size if it is 1 TB/TiB or higher\n\n`sb`\n: the units of a file’s size (sets `ub`, `uk`, `um`, `ug` and `uh`)\n\n`ub`\n: the units of a file’s size if it is lower than 1 KB/Kib\n\n`uk`\n: the units of a file’s size if it is between 1 KB/KiB and 1 MB/MiB\n\n`um`\n: the units of a file’s size if it is between 1 MB/MiB and 1 GB/GiB\n\n`ug`\n: the units of a file’s size if it is between 1 GB/GiB and 1 TB/TiB\n\n`ut`\n: the units of a file’s size if it is 1 TB/TiB or higher\n\n`df`\n: a device’s major ID\n\n`ds`\n: a device’s minor ID\n\n`uu`\n: a user that’s you\n\n`un`\n: a user that’s someone else\n\n`gu`\n: a group that you belong to\n\n`gn`\n: a group you aren’t a member of\n\n`lc`\n: a number of hard links\n\n`lm`\n: a number of hard links for a regular file with at least two\n\n`ga`\n: a new flag in Git\n\n`gm`\n: a modified flag in Git\n\n`gd`\n: a deleted flag in Git\n\n`gv`\n: a renamed flag in Git\n\n`gt`\n: a modified metadata flag in Git\n\n`xx`\n: “punctuation”, including many background UI elements\n\n`da`\n: a file’s date\n\n`in`\n: a file’s inode number\n\n`bl`\n: a file’s number of blocks\n\n`hd`\n: the header row of a table\n\n`lp`\n: the path of a symlink\n\n`cc`\n: an escaped character in a filename\n\n`bO`\n: the overlay style for broken symlink paths\n\nValues in `EXA_COLORS` override those given in `LS_COLORS`, so you don’t need to re-write an existing `LS_COLORS` variable with proprietary extensions.\n\n\nLIST OF STYLES\n==============\n\nUnlike some versions of `ls`, the given ANSI values must be valid colour codes: exa won’t just print out whichever characters are given.\n\nThe codes accepted by exa are:\n\n`1`\n: for bold\n\n`4`\n: for underline\n\n`31`\n: for red text\n\n`32`\n: for green text\n\n`33`\n: for yellow text\n\n`34`\n: for blue text\n\n`35`\n: for purple text\n\n`36`\n: for cyan text\n\n`37`\n: for white text\n\n`38;5;nnn`\n: for a colour from 0 to 255 (replace the `nnn` part)\n\nMany terminals will treat bolded text as a different colour, or at least provide the option to.\n\nexa provides its own built-in set of file extension mappings that cover a large range of common file extensions, including documents, archives, media, and temporary files.\nAny mappings in the environment variables will override this default set: running exa with `LS_COLORS=\"*.zip=32\"` will turn zip files green but leave the colours of other compressed files alone.\n\nYou can also disable this built-in set entirely by including a `reset` entry at the beginning of `EXA_COLORS`.\nSo setting `EXA_COLORS=\"reset:*.txt=31\"` will highlight only text files; setting `EXA_COLORS=\"reset\"` will highlight nothing.\n\n\nAUTHOR\n======\n\nexa is maintained by Benjamin ‘ogham’ Sago and many other contributors.\n\n**Website:** `https://the.exa.website/` \\\n**Source code:** `https://github.com/ogham/exa` \\\n**Contributors:** `https://github.com/ogham/exa/graphs/contributors`\n\n\nSEE ALSO\n========\n\n- `exa(1)`\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.66.1\"\n"
  },
  {
    "path": "snap/.gitignore",
    "content": ".snapcraft\n"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "name: exa\nversion: 'latest'\nsummary: Replacement for 'ls' written in Rust\ndescription: |\n  It uses colours for information by default, helping you distinguish between\n  many types of files, such as whether you are the owner, or in the owning\n  group. It also has extra features not present in the original ls, such as\n  viewing the Git status for a directory, or recursing into directories with a\n  tree view. exa is written in Rust, so it’s small, fast, and portable.\n\ngrade: stable\nconfinement: classic\n\napps:\n  exa:\n    command: exa\n\nparts:\n  exa:\n    plugin: rust\n    source: .\n    stage-packages:\n      - libgit2-24\n      - cmake\n      - libz-dev\n"
  },
  {
    "path": "src/fs/dir.rs",
    "content": "use crate::fs::feature::git::GitCache;\nuse crate::fs::fields::GitStatus;\nuse std::io;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::slice::Iter as SliceIter;\n\nuse log::*;\n\nuse crate::fs::File;\n\n\n/// A **Dir** provides a cached list of the file paths in a directory that’s\n/// being listed.\n///\n/// This object gets passed to the Files themselves, in order for them to\n/// check the existence of surrounding files, then highlight themselves\n/// accordingly. (See `File#get_source_files`)\npub struct Dir {\n\n    /// A vector of the files that have been read from this directory.\n    contents: Vec<PathBuf>,\n\n    /// The path that was read.\n    pub path: PathBuf,\n}\n\nimpl Dir {\n\n    /// Create a new Dir object filled with all the files in the directory\n    /// pointed to by the given path. Fails if the directory can’t be read, or\n    /// isn’t actually a directory, or if there’s an IO error that occurs at\n    /// any point.\n    ///\n    /// The `read_dir` iterator doesn’t actually yield the `.` and `..`\n    /// entries, so if the user wants to see them, we’ll have to add them\n    /// ourselves after the files have been read.\n    pub fn read_dir(path: PathBuf) -> io::Result<Self> {\n        info!(\"Reading directory {:?}\", &path);\n\n        let contents = fs::read_dir(&path)?\n                          .map(|result| result.map(|entry| entry.path()))\n                          .collect::<Result<_, _>>()?;\n\n        Ok(Self { contents, path })\n    }\n\n    /// Produce an iterator of IO results of trying to read all the files in\n    /// this directory.\n    pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool) -> Files<'dir, 'ig> {\n        Files {\n            inner:     self.contents.iter(),\n            dir:       self,\n            dotfiles:  dots.shows_dotfiles(),\n            dots:      dots.dots(),\n            git,\n            git_ignoring,\n        }\n    }\n\n    /// Whether this directory contains a file with the given path.\n    pub fn contains(&self, path: &Path) -> bool {\n        self.contents.iter().any(|p| p.as_path() == path)\n    }\n\n    /// Append a path onto the path specified by this directory.\n    pub fn join(&self, child: &Path) -> PathBuf {\n        self.path.join(child)\n    }\n}\n\n\n/// Iterator over reading the contents of a directory as `File` objects.\npub struct Files<'dir, 'ig> {\n\n    /// The internal iterator over the paths that have been read already.\n    inner: SliceIter<'dir, PathBuf>,\n\n    /// The directory that begat those paths.\n    dir: &'dir Dir,\n\n    /// Whether to include dotfiles in the list.\n    dotfiles: bool,\n\n    /// Whether the `.` or `..` directories should be produced first, before\n    /// any files have been listed.\n    dots: DotsNext,\n\n    git: Option<&'ig GitCache>,\n\n    git_ignoring: bool,\n}\n\nimpl<'dir, 'ig> Files<'dir, 'ig> {\n    fn parent(&self) -> PathBuf {\n        // We can’t use `Path#parent` here because all it does is remove the\n        // last path component, which is no good for us if the path is\n        // relative. For example, while the parent of `/testcases/files` is\n        // `/testcases`, the parent of `.` is an empty path. Adding `..` on\n        // the end is the only way to get to the *actual* parent directory.\n        self.dir.path.join(\"..\")\n    }\n\n    /// Go through the directory until we encounter a file we can list (which\n    /// varies depending on the dotfile visibility flag)\n    fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {\n        loop {\n            if let Some(path) = self.inner.next() {\n                let filename = File::filename(path);\n                if ! self.dotfiles && filename.starts_with('.') {\n                    continue;\n                }\n\n                // Also hide _prefix files on Windows because it's used by old applications\n                // as an alternative to dot-prefix files.\n                #[cfg(windows)]\n                if ! self.dotfiles && filename.starts_with('_') {\n                    continue;\n                }\n\n                if self.git_ignoring {\n                    let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();\n                    if git_status.unstaged == GitStatus::Ignored {\n                         continue;\n                    }\n                }\n\n                return Some(File::from_args(path.clone(), self.dir, filename)\n                                 .map_err(|e| (path.clone(), e)))\n            }\n\n            return None\n        }\n    }\n}\n\n/// The dot directories that need to be listed before actual files, if any.\n/// If these aren’t being printed, then `FilesNext` is used to skip them.\nenum DotsNext {\n\n    /// List the `.` directory next.\n    Dot,\n\n    /// List the `..` directory next.\n    DotDot,\n\n    /// Forget about the dot directories and just list files.\n    Files,\n}\n\nimpl<'dir, 'ig> Iterator for Files<'dir, 'ig> {\n    type Item = Result<File<'dir>, (PathBuf, io::Error)>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        match self.dots {\n            DotsNext::Dot => {\n                self.dots = DotsNext::DotDot;\n                Some(File::new_aa_current(self.dir)\n                          .map_err(|e| (Path::new(\".\").to_path_buf(), e)))\n            }\n\n            DotsNext::DotDot => {\n                self.dots = DotsNext::Files;\n                Some(File::new_aa_parent(self.parent(), self.dir)\n                          .map_err(|e| (self.parent(), e)))\n            }\n\n            DotsNext::Files => {\n                self.next_visible_file()\n            }\n        }\n    }\n}\n\n\n/// Usually files in Unix use a leading dot to be hidden or visible, but two\n/// entries in particular are “extra-hidden”: `.` and `..`, which only become\n/// visible after an extra `-a` option.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum DotFilter {\n\n    /// Shows files, dotfiles, and `.` and `..`.\n    DotfilesAndDots,\n\n    /// Show files and dotfiles, but hide `.` and `..`.\n    Dotfiles,\n\n    /// Just show files, hiding anything beginning with a dot.\n    JustFiles,\n}\n\nimpl Default for DotFilter {\n    fn default() -> Self {\n        Self::JustFiles\n    }\n}\n\nimpl DotFilter {\n\n    /// Whether this filter should show dotfiles in a listing.\n    fn shows_dotfiles(self) -> bool {\n        match self {\n            Self::JustFiles       => false,\n            Self::Dotfiles        => true,\n            Self::DotfilesAndDots => true,\n        }\n    }\n\n    /// Whether this filter should add dot directories to a listing.\n    fn dots(self) -> DotsNext {\n        match self {\n            Self::JustFiles        => DotsNext::Files,\n            Self::Dotfiles         => DotsNext::Files,\n            Self::DotfilesAndDots  => DotsNext::Dot,\n        }\n    }\n}\n"
  },
  {
    "path": "src/fs/dir_action.rs",
    "content": "//! What to do when encountering a directory?\n\n/// The action to take when trying to list a file that turns out to be a\n/// directory.\n///\n/// By default, exa will display the information about files passed in as\n/// command-line arguments, with one file per entry. However, if a directory\n/// is passed in, exa assumes that the user wants to see its contents, rather\n/// than the directory itself.\n///\n/// This can get annoying sometimes: if a user does `exa ~/Downloads/img-*`\n/// to see the details of every file starting with `img-`, any directories\n/// that happen to start with the same will be listed after the files at\n/// the end in a separate block. By listing directories as files, their\n/// directory status will be ignored, and both will be listed side-by-side.\n///\n/// These two modes have recursive analogues in the “recurse” and “tree”\n/// modes. Here, instead of just listing the directories, exa will descend\n/// into them and print out their contents. The recurse mode does this by\n/// having extra output blocks at the end, while the tree mode will show\n/// directories inline, with their contents immediately underneath.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum DirAction {\n\n    /// This directory should be listed along with the regular files, instead\n    /// of having its contents queried.\n    AsFile,\n\n    /// This directory should not be listed, and should instead be opened and\n    /// *its* files listed separately. This is the default behaviour.\n    List,\n\n    /// This directory should be listed along with the regular files, and then\n    /// its contents should be listed afterward. The recursive contents of\n    /// *those* contents are dictated by the options argument.\n    Recurse(RecurseOptions),\n}\n\nimpl DirAction {\n\n    /// Gets the recurse options, if this dir action has any.\n    pub fn recurse_options(self) -> Option<RecurseOptions> {\n        match self {\n            Self::Recurse(o)  => Some(o),\n            _                 => None,\n        }\n    }\n\n    /// Whether to treat directories as regular files or not.\n    pub fn treat_dirs_as_files(self) -> bool {\n        match self {\n            Self::AsFile      => true,\n            Self::Recurse(o)  => o.tree,\n            Self::List        => false,\n        }\n    }\n}\n\n\n/// The options that determine how to recurse into a directory.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub struct RecurseOptions {\n\n    /// Whether recursion should be done as a tree or as multiple individual\n    /// views of files.\n    pub tree: bool,\n\n    /// The maximum number of times that recursion should descend to, if one\n    /// is specified.\n    pub max_depth: Option<usize>,\n}\n\nimpl RecurseOptions {\n\n    /// Returns whether a directory of the given depth would be too deep.\n    pub fn is_too_deep(self, depth: usize) -> bool {\n        match self.max_depth {\n            None     => false,\n            Some(d)  => d <= depth\n        }\n    }\n}\n"
  },
  {
    "path": "src/fs/feature/git.rs",
    "content": "//! Getting the Git status of files and directories.\n\nuse std::ffi::OsStr;\n#[cfg(target_family = \"unix\")]\nuse std::os::unix::ffi::OsStrExt;\nuse std::path::{Path, PathBuf};\nuse std::sync::Mutex;\n\nuse log::*;\n\nuse crate::fs::fields as f;\n\n\n/// A **Git cache** is assembled based on the user’s input arguments.\n///\n/// This uses vectors to avoid the overhead of hashing: it’s not worth it when the\n/// expected number of Git repositories per exa invocation is 0 or 1...\npub struct GitCache {\n\n    /// A list of discovered Git repositories and their paths.\n    repos: Vec<GitRepo>,\n\n    /// Paths that we’ve confirmed do not have Git repositories underneath them.\n    misses: Vec<PathBuf>,\n}\n\nimpl GitCache {\n    pub fn has_anything_for(&self, index: &Path) -> bool {\n        self.repos.iter().any(|e| e.has_path(index))\n    }\n\n    pub fn get(&self, index: &Path, prefix_lookup: bool) -> f::Git {\n        self.repos.iter()\n            .find(|e| e.has_path(index))\n            .map(|repo| repo.search(index, prefix_lookup))\n            .unwrap_or_default()\n    }\n}\n\nuse std::iter::FromIterator;\nimpl FromIterator<PathBuf> for GitCache {\n    fn from_iter<I>(iter: I) -> Self\n    where I: IntoIterator<Item=PathBuf>\n    {\n        let iter = iter.into_iter();\n        let mut git = Self {\n            repos: Vec::with_capacity(iter.size_hint().0),\n            misses: Vec::new(),\n        };\n\n        for path in iter {\n            if git.misses.contains(&path) {\n                debug!(\"Skipping {:?} because it already came back Gitless\", path);\n            }\n            else if git.repos.iter().any(|e| e.has_path(&path)) {\n                debug!(\"Skipping {:?} because we already queried it\", path);\n            }\n            else {\n                match GitRepo::discover(path) {\n                    Ok(r) => {\n                        if let Some(r2) = git.repos.iter_mut().find(|e| e.has_workdir(&r.workdir)) {\n                            debug!(\"Adding to existing repo (workdir matches with {:?})\", r2.workdir);\n                            r2.extra_paths.push(r.original_path);\n                            continue;\n                        }\n\n                        debug!(\"Discovered new Git repo\");\n                        git.repos.push(r);\n                    }\n                    Err(miss) => {\n                        git.misses.push(miss)\n                    }\n                }\n            }\n        }\n\n        git\n    }\n}\n\n\n/// A **Git repository** is one we’ve discovered somewhere on the filesystem.\npub struct GitRepo {\n\n    /// The queryable contents of the repository: either a `git2` repo, or the\n    /// cached results from when we queried it last time.\n    contents: Mutex<GitContents>,\n\n    /// The working directory of this repository.\n    /// This is used to check whether two repositories are the same.\n    workdir: PathBuf,\n\n    /// The path that was originally checked to discover this repository.\n    /// This is as important as the extra_paths (it gets checked first), but\n    /// is separate to avoid having to deal with a non-empty Vec.\n    original_path: PathBuf,\n\n    /// Any other paths that were checked only to result in this same\n    /// repository.\n    extra_paths: Vec<PathBuf>,\n}\n\n/// A repository’s queried state.\nenum GitContents {\n\n    /// All the interesting Git stuff goes through this.\n    Before {\n        repo: git2::Repository,\n    },\n\n    /// Temporary value used in `repo_to_statuses` so we can move the\n    /// repository out of the `Before` variant.\n    Processing,\n\n    /// The data we’ve extracted from the repository, but only after we’ve\n    /// actually done so.\n    After {\n        statuses: Git,\n    },\n}\n\nimpl GitRepo {\n\n    /// Searches through this repository for a path (to a file or directory,\n    /// depending on the prefix-lookup flag) and returns its Git status.\n    ///\n    /// Actually querying the `git2` repository for the mapping of paths to\n    /// Git statuses is only done once, and gets cached so we don’t need to\n    /// re-query the entire repository the times after that.\n    ///\n    /// The temporary `Processing` enum variant is used after the `git2`\n    /// repository is moved out, but before the results have been moved in!\n    /// See <https://stackoverflow.com/q/45985827/3484614>\n    fn search(&self, index: &Path, prefix_lookup: bool) -> f::Git {\n        use std::mem::replace;\n\n        let mut contents = self.contents.lock().unwrap();\n        if let GitContents::After { ref statuses } = *contents {\n            debug!(\"Git repo {:?} has been found in cache\", &self.workdir);\n            return statuses.status(index, prefix_lookup);\n        }\n\n        debug!(\"Querying Git repo {:?} for the first time\", &self.workdir);\n        let repo = replace(&mut *contents, GitContents::Processing).inner_repo();\n        let statuses = repo_to_statuses(&repo, &self.workdir);\n        let result = statuses.status(index, prefix_lookup);\n        let _processing = replace(&mut *contents, GitContents::After { statuses });\n        result\n    }\n\n    /// Whether this repository has the given working directory.\n    fn has_workdir(&self, path: &Path) -> bool {\n        self.workdir == path\n    }\n\n    /// Whether this repository cares about the given path at all.\n    fn has_path(&self, path: &Path) -> bool {\n        path.starts_with(&self.original_path) || self.extra_paths.iter().any(|e| path.starts_with(e))\n    }\n\n    /// Searches for a Git repository at any point above the given path.\n    /// Returns the original buffer if none is found.\n    fn discover(path: PathBuf) -> Result<Self, PathBuf> {\n        info!(\"Searching for Git repository above {:?}\", path);\n        let repo = match git2::Repository::discover(&path) {\n            Ok(r) => r,\n            Err(e) => {\n                error!(\"Error discovering Git repositories: {:?}\", e);\n                return Err(path);\n            }\n        };\n\n        if let Some(workdir) = repo.workdir() {\n            let workdir = workdir.to_path_buf();\n            let contents = Mutex::new(GitContents::Before { repo });\n            Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() })\n        }\n        else {\n            warn!(\"Repository has no workdir?\");\n            Err(path)\n        }\n    }\n}\n\n\nimpl GitContents {\n    /// Assumes that the repository hasn’t been queried, and extracts it\n    /// (consuming the value) if it has. This is needed because the entire\n    /// enum variant gets replaced when a repo is queried (see above).\n    fn inner_repo(self) -> git2::Repository {\n        if let Self::Before { repo } = self {\n            repo\n        }\n        else {\n            unreachable!(\"Tried to extract a non-Repository\")\n        }\n    }\n}\n\n/// Iterates through a repository’s statuses, consuming it and returning the\n/// mapping of files to their Git status.\n/// We will have already used the working directory at this point, so it gets\n/// passed in rather than deriving it from the `Repository` again.\nfn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {\n    let mut statuses = Vec::new();\n\n    info!(\"Getting Git statuses for repo with workdir {:?}\", workdir);\n    match repo.statuses(None) {\n        Ok(es) => {\n            for e in es.iter() {\n                #[cfg(target_family = \"unix\")]\n                let path = workdir.join(Path::new(OsStr::from_bytes(e.path_bytes())));\n                // TODO: handle non Unix systems better:\n                // https://github.com/ogham/exa/issues/698\n                #[cfg(not(target_family = \"unix\"))]\n                let path = workdir.join(Path::new(e.path().unwrap()));\n                let elem = (path, e.status());\n                statuses.push(elem);\n            }\n        }\n        Err(e) => {\n            error!(\"Error looking up Git statuses: {:?}\", e);\n        }\n    }\n\n    Git { statuses }\n}\n\n// The `repo.statuses` call above takes a long time. exa debug output:\n//\n//   20.311276  INFO:exa::fs::feature::git: Getting Git statuses for repo with workdir \"/vagrant/\"\n//   20.799610  DEBUG:exa::output::table: Getting Git status for file \"./Cargo.toml\"\n//\n// Even inserting another logging line immediately afterwards doesn’t make it\n// look any faster.\n\n\n/// Container of Git statuses for all the files in this folder’s Git repository.\nstruct Git {\n    statuses: Vec<(PathBuf, git2::Status)>,\n}\n\nimpl Git {\n\n    /// Get either the file or directory status for the given path.\n    /// “Prefix lookup” means that it should report an aggregate status of all\n    /// paths starting with the given prefix (in other words, a directory).\n    fn status(&self, index: &Path, prefix_lookup: bool) -> f::Git {\n        if prefix_lookup { self.dir_status(index) }\n                    else { self.file_status(index) }\n    }\n\n    /// Get the user-facing status of a file.\n    /// We check the statuses directly applying to a file, and for the ignored\n    /// status we check if any of its parents directories is ignored by git.\n    fn file_status(&self, file: &Path) -> f::Git {\n        let path = reorient(file);\n\n        let s = self.statuses.iter()\n            .filter(|p| if p.1 == git2::Status::IGNORED {\n                path.starts_with(&p.0)\n            } else {\n                p.0 == path\n            })\n            .fold(git2::Status::empty(), |a, b| a | b.1);\n\n        let staged = index_status(s);\n        let unstaged = working_tree_status(s);\n        f::Git { staged, unstaged }\n    }\n\n    /// Get the combined, user-facing status of a directory.\n    /// Statuses are aggregating (for example, a directory is considered\n    /// modified if any file under it has the status modified), except for\n    /// ignored status which applies to files under (for example, a directory\n    /// is considered ignored if one of its parent directories is ignored).\n    fn dir_status(&self, dir: &Path) -> f::Git {\n        let path = reorient(dir);\n\n        let s = self.statuses.iter()\n            .filter(|p| if p.1 == git2::Status::IGNORED {\n                path.starts_with(&p.0)\n            } else {\n                p.0.starts_with(&path)\n            })\n            .fold(git2::Status::empty(), |a, b| a | b.1);\n\n        let staged = index_status(s);\n        let unstaged = working_tree_status(s);\n        f::Git { staged, unstaged }\n    }\n}\n\n\n/// Converts a path to an absolute path based on the current directory.\n/// Paths need to be absolute for them to be compared properly, otherwise\n/// you’d ask a repo about “./README.md” but it only knows about\n/// “/vagrant/README.md”, prefixed by the workdir.\n#[cfg(unix)]\nfn reorient(path: &Path) -> PathBuf {\n    use std::env::current_dir;\n\n    // TODO: I’m not 100% on this func tbh\n    let path = match current_dir() {\n        Err(_)   => Path::new(\".\").join(&path),\n        Ok(dir)  => dir.join(&path),\n    };\n\n    path.canonicalize().unwrap_or(path)\n}\n\n#[cfg(windows)]\nfn reorient(path: &Path) -> PathBuf {\n    let unc_path = path.canonicalize().unwrap();\n    // On Windows UNC path is returned. We need to strip the prefix for it to work.\n    let normal_path = unc_path.as_os_str().to_str().unwrap().trim_left_matches(\"\\\\\\\\?\\\\\");\n    return PathBuf::from(normal_path);\n}\n\n/// The character to display if the file has been modified, but not staged.\nfn working_tree_status(status: git2::Status) -> f::GitStatus {\n    match status {\n        s if s.contains(git2::Status::WT_NEW)         => f::GitStatus::New,\n        s if s.contains(git2::Status::WT_MODIFIED)    => f::GitStatus::Modified,\n        s if s.contains(git2::Status::WT_DELETED)     => f::GitStatus::Deleted,\n        s if s.contains(git2::Status::WT_RENAMED)     => f::GitStatus::Renamed,\n        s if s.contains(git2::Status::WT_TYPECHANGE)  => f::GitStatus::TypeChange,\n        s if s.contains(git2::Status::IGNORED)        => f::GitStatus::Ignored,\n        s if s.contains(git2::Status::CONFLICTED)     => f::GitStatus::Conflicted,\n        _                                             => f::GitStatus::NotModified,\n    }\n}\n\n/// The character to display if the file has been modified and the change\n/// has been staged.\nfn index_status(status: git2::Status) -> f::GitStatus {\n    match status {\n        s if s.contains(git2::Status::INDEX_NEW)         => f::GitStatus::New,\n        s if s.contains(git2::Status::INDEX_MODIFIED)    => f::GitStatus::Modified,\n        s if s.contains(git2::Status::INDEX_DELETED)     => f::GitStatus::Deleted,\n        s if s.contains(git2::Status::INDEX_RENAMED)     => f::GitStatus::Renamed,\n        s if s.contains(git2::Status::INDEX_TYPECHANGE)  => f::GitStatus::TypeChange,\n        _                                                => f::GitStatus::NotModified,\n    }\n}\n"
  },
  {
    "path": "src/fs/feature/mod.rs",
    "content": "pub mod xattr;\n\n#[cfg(feature = \"git\")]\npub mod git;\n\n#[cfg(not(feature = \"git\"))]\npub mod git {\n    use std::iter::FromIterator;\n    use std::path::{Path, PathBuf};\n\n    use crate::fs::fields as f;\n\n\n    pub struct GitCache;\n\n    impl FromIterator<PathBuf> for GitCache {\n        fn from_iter<I>(_iter: I) -> Self\n        where I: IntoIterator<Item=PathBuf>\n        {\n            Self\n        }\n    }\n\n    impl GitCache {\n        pub fn has_anything_for(&self, _index: &Path) -> bool {\n            false\n        }\n\n        pub fn get(&self, _index: &Path, _prefix_lookup: bool) -> f::Git {\n            unreachable!();\n        }\n    }\n}\n"
  },
  {
    "path": "src/fs/feature/xattr.rs",
    "content": "//! Extended attribute support for Darwin and Linux systems.\n\n#![allow(trivial_casts)]  // for ARM\n\nuse std::cmp::Ordering;\nuse std::io;\nuse std::path::Path;\n\n\npub const ENABLED: bool = cfg!(any(target_os = \"macos\", target_os = \"linux\"));\n\n\npub trait FileAttributes {\n    fn attributes(&self) -> io::Result<Vec<Attribute>>;\n    fn symlink_attributes(&self) -> io::Result<Vec<Attribute>>;\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"linux\"))]\nimpl FileAttributes for Path {\n    fn attributes(&self) -> io::Result<Vec<Attribute>> {\n        list_attrs(&lister::Lister::new(FollowSymlinks::Yes), self)\n    }\n\n    fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {\n        list_attrs(&lister::Lister::new(FollowSymlinks::No), self)\n    }\n}\n\n#[cfg(not(any(target_os = \"macos\", target_os = \"linux\")))]\nimpl FileAttributes for Path {\n    fn attributes(&self) -> io::Result<Vec<Attribute>> {\n        Ok(Vec::new())\n    }\n\n    fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {\n        Ok(Vec::new())\n    }\n}\n\n\n/// Attributes which can be passed to `Attribute::list_with_flags`\n#[cfg(any(target_os = \"macos\", target_os = \"linux\"))]\n#[derive(Copy, Clone)]\npub enum FollowSymlinks {\n    Yes,\n    No,\n}\n\n/// Extended attribute\n#[derive(Debug, Clone)]\npub struct Attribute {\n    pub name: String,\n    pub size: usize,\n}\n\n\n#[cfg(any(target_os = \"macos\", target_os = \"linux\"))]\npub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {\n    use std::ffi::CString;\n\n    let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {\n        Some(cstring) => cstring,\n        None => {\n            return Err(io::Error::new(io::ErrorKind::Other, \"Error: path somehow contained a NUL?\"));\n        }\n    };\n\n    let bufsize = lister.listxattr_first(&c_path);\n    match bufsize.cmp(&0) {\n        Ordering::Less     => return Err(io::Error::last_os_error()),\n        Ordering::Equal    => return Ok(Vec::new()),\n        Ordering::Greater  => {},\n    }\n\n    let mut buf = vec![0_u8; bufsize as usize];\n    let err = lister.listxattr_second(&c_path, &mut buf, bufsize);\n\n    match err.cmp(&0) {\n        Ordering::Less     => return Err(io::Error::last_os_error()),\n        Ordering::Equal    => return Ok(Vec::new()),\n        Ordering::Greater  => {},\n    }\n\n    let mut names = Vec::new();\n    if err > 0 {\n        // End indices of the attribute names\n        // the buffer contains 0-terminated c-strings\n        let idx = buf.iter().enumerate().filter_map(|(i, v)|\n            if *v == 0 { Some(i) } else { None }\n        );\n        let mut start = 0;\n\n        for end in idx {\n            let c_end = end + 1; // end of the c-string (including 0)\n            let size = lister.getxattr(&c_path, &buf[start..c_end]);\n\n            if size > 0 {\n                names.push(Attribute {\n                    name: lister.translate_attribute_name(&buf[start..end]),\n                    size: size as usize,\n                });\n            }\n\n            start = c_end;\n        }\n    }\n\n    Ok(names)\n}\n\n\n#[cfg(target_os = \"macos\")]\nmod lister {\n    use super::FollowSymlinks;\n    use libc::{c_int, size_t, ssize_t, c_char, c_void};\n    use std::ffi::CString;\n    use std::ptr;\n\n    extern \"C\" {\n        fn listxattr(\n            path: *const c_char,\n            namebuf: *mut c_char,\n            size: size_t,\n            options: c_int,\n        ) -> ssize_t;\n\n        fn getxattr(\n            path: *const c_char,\n            name: *const c_char,\n            value: *mut c_void,\n            size: size_t,\n            position: u32,\n            options: c_int,\n        ) -> ssize_t;\n    }\n\n    pub struct Lister {\n        c_flags: c_int,\n    }\n\n    impl Lister {\n        pub fn new(do_follow: FollowSymlinks) -> Self {\n            let c_flags: c_int = match do_follow {\n                FollowSymlinks::Yes  => 0x0001,\n                FollowSymlinks::No   => 0x0000,\n            };\n\n            Self { c_flags }\n        }\n\n        pub fn translate_attribute_name(&self, input: &[u8]) -> String {\n            unsafe { std::str::from_utf8_unchecked(input).into() }\n        }\n\n        pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {\n            unsafe {\n                listxattr(\n                    c_path.as_ptr(),\n                    ptr::null_mut(),\n                    0,\n                    self.c_flags,\n                )\n            }\n        }\n\n        pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {\n            unsafe {\n                listxattr(\n                    c_path.as_ptr(),\n                    buf.as_mut_ptr().cast::<c_char>(),\n                    bufsize as size_t,\n                    self.c_flags,\n                )\n            }\n        }\n\n        pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {\n            unsafe {\n                getxattr(\n                    c_path.as_ptr(),\n                    buf.as_ptr().cast::<c_char>(),\n                    ptr::null_mut(),\n                    0,\n                    0,\n                    self.c_flags,\n                )\n            }\n        }\n    }\n}\n\n\n#[cfg(target_os = \"linux\")]\nmod lister {\n    use std::ffi::CString;\n    use libc::{size_t, ssize_t, c_char, c_void};\n    use super::FollowSymlinks;\n    use std::ptr;\n\n    extern \"C\" {\n        fn listxattr(\n            path: *const c_char,\n            list: *mut c_char,\n            size: size_t,\n        ) -> ssize_t;\n\n        fn llistxattr(\n            path: *const c_char,\n            list: *mut c_char,\n            size: size_t,\n        ) -> ssize_t;\n\n        fn getxattr(\n            path: *const c_char,\n            name: *const c_char,\n            value: *mut c_void,\n            size: size_t,\n        ) -> ssize_t;\n\n        fn lgetxattr(\n            path: *const c_char,\n            name: *const c_char,\n            value: *mut c_void,\n            size: size_t,\n        ) -> ssize_t;\n    }\n\n    pub struct Lister {\n        follow_symlinks: FollowSymlinks,\n    }\n\n    impl Lister {\n        pub fn new(follow_symlinks: FollowSymlinks) -> Lister {\n            Lister { follow_symlinks }\n        }\n\n        pub fn translate_attribute_name(&self, input: &[u8]) -> String {\n            String::from_utf8_lossy(input).into_owned()\n        }\n\n        pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {\n            let listxattr = match self.follow_symlinks {\n                FollowSymlinks::Yes  => listxattr,\n                FollowSymlinks::No   => llistxattr,\n            };\n\n            unsafe {\n                listxattr(\n                    c_path.as_ptr().cast(),\n                    ptr::null_mut(),\n                    0,\n                )\n            }\n        }\n\n        pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {\n            let listxattr = match self.follow_symlinks {\n                FollowSymlinks::Yes  => listxattr,\n                FollowSymlinks::No   => llistxattr,\n            };\n\n            unsafe {\n                listxattr(\n                    c_path.as_ptr().cast(),\n                    buf.as_mut_ptr().cast(),\n                    bufsize as size_t,\n                )\n            }\n        }\n\n        pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {\n            let getxattr = match self.follow_symlinks {\n                FollowSymlinks::Yes  => getxattr,\n                FollowSymlinks::No   => lgetxattr,\n            };\n\n            unsafe {\n                getxattr(\n                    c_path.as_ptr().cast(),\n                    buf.as_ptr().cast(),\n                    ptr::null_mut(),\n                    0,\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/fs/fields.rs",
    "content": "//! Wrapper types for the values returned from `File`s.\n//!\n//! The methods of `File` that return information about the entry on the\n//! filesystem -- size, modification date, block count, or Git status -- used\n//! to just return these as formatted strings, but this became inflexible once\n//! customisable output styles landed.\n//!\n//! Instead, they will return a wrapper type from this module, which tags the\n//! type with what field it is while containing the actual raw value.\n//!\n//! The `output::details` module, among others, uses these types to render and\n//! display the information as formatted strings.\n\n// C-style `blkcnt_t` types don’t follow Rust’s rules!\n#![allow(non_camel_case_types)]\n#![allow(clippy::struct_excessive_bools)]\n\n\n/// The type of a file’s block count.\npub type blkcnt_t = u64;\n\n/// The type of a file’s group ID.\npub type gid_t = u32;\n\n/// The type of a file’s inode.\npub type ino_t = u64;\n\n/// The type of a file’s number of links.\npub type nlink_t = u64;\n\n/// The type of a file’s timestamp (creation, modification, access, etc).\npub type time_t = i64;\n\n/// The type of a file’s user ID.\npub type uid_t = u32;\n\n\n/// The file’s base type, which gets displayed in the very first column of the\n/// details output.\n///\n/// This type is set entirely by the filesystem, rather than relying on a\n/// file’s contents. So “link” is a type, but “image” is just a type of\n/// regular file. (See the `filetype` module for those checks.)\n///\n/// Its ordering is used when sorting by type.\n#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]\npub enum Type {\n    Directory,\n    File,\n    Link,\n    Pipe,\n    Socket,\n    CharDevice,\n    BlockDevice,\n    Special,\n}\n\nimpl Type {\n    pub fn is_regular_file(self) -> bool {\n        matches!(self, Self::File)\n    }\n}\n\n\n/// The file’s Unix permission bitfield, with one entry per bit.\n#[derive(Copy, Clone)]\npub struct Permissions {\n    pub user_read:      bool,\n    pub user_write:     bool,\n    pub user_execute:   bool,\n\n    pub group_read:     bool,\n    pub group_write:    bool,\n    pub group_execute:  bool,\n\n    pub other_read:     bool,\n    pub other_write:    bool,\n    pub other_execute:  bool,\n\n    pub sticky:         bool,\n    pub setgid:         bool,\n    pub setuid:         bool,\n}\n\n/// The file's FileAttributes field, available only on Windows.\n#[derive(Copy, Clone)]\npub struct Attributes {\n    pub archive:         bool,\n    pub directory:       bool,\n    pub readonly:        bool,\n    pub hidden:          bool,\n    pub system:          bool,\n    pub reparse_point:   bool,\n}\n\n/// The three pieces of information that are displayed as a single column in\n/// the details view. These values are fused together to make the output a\n/// little more compressed.\n#[derive(Copy, Clone)]\npub struct PermissionsPlus {\n    pub file_type:   Type,\n    #[cfg(unix)]\n    pub permissions: Permissions,\n    #[cfg(windows)]\n    pub attributes:  Attributes,\n    pub xattrs:      bool,\n}\n\n\n/// The permissions encoded as octal values\n#[derive(Copy, Clone)]\npub struct OctalPermissions {\n    pub permissions: Permissions,\n}\n\n/// A file’s number of hard links on the filesystem.\n///\n/// Under Unix, a file can exist on the filesystem only once but appear in\n/// multiple directories. However, it’s rare (but occasionally useful!) for a\n/// regular file to have a link count greater than 1, so we highlight the\n/// block count specifically for this case.\n#[derive(Copy, Clone)]\npub struct Links {\n\n    /// The actual link count.\n    pub count: nlink_t,\n\n    /// Whether this file is a regular file with more than one hard link.\n    pub multiple: bool,\n}\n\n\n/// A file’s inode. Every directory entry on a Unix filesystem has an inode,\n/// including directories and links, so this is applicable to everything exa\n/// can deal with.\n#[derive(Copy, Clone)]\npub struct Inode(pub ino_t);\n\n\n/// The number of blocks that a file takes up on the filesystem, if any.\n#[derive(Copy, Clone)]\npub enum Blocks {\n\n    /// This file has the given number of blocks.\n    Some(blkcnt_t),\n\n    /// This file isn’t of a type that can take up blocks.\n    None,\n}\n\n\n/// The ID of the user that owns a file. This will only ever be a number;\n/// looking up the username is done in the `display` module.\n#[derive(Copy, Clone)]\npub struct User(pub uid_t);\n\n/// The ID of the group that a file belongs to.\n#[derive(Copy, Clone)]\npub struct Group(pub gid_t);\n\n\n/// A file’s size, in bytes. This is usually formatted by the `number_prefix`\n/// crate into something human-readable.\n#[derive(Copy, Clone)]\npub enum Size {\n\n    /// This file has a defined size.\n    Some(u64),\n\n    /// This file has no size, or has a size but we aren’t interested in it.\n    ///\n    /// Under Unix, directory entries that aren’t regular files will still\n    /// have a file size. For example, a directory will just contain a list of\n    /// its files as its “contents” and will be specially flagged as being a\n    /// directory, rather than a file. However, seeing the “file size” of this\n    /// data is rarely useful — I can’t think of a time when I’ve seen it and\n    /// learnt something. So we discard it and just output “-” instead.\n    ///\n    /// See this answer for more: <https://unix.stackexchange.com/a/68266>\n    None,\n\n    /// This file is a block or character device, so instead of a size, print\n    /// out the file’s major and minor device IDs.\n    ///\n    /// This is what ls does as well. Without it, the devices will just have\n    /// file sizes of zero.\n    DeviceIDs(DeviceIDs),\n}\n\n/// The major and minor device IDs that gets displayed for device files.\n///\n/// You can see what these device numbers mean:\n/// - <http://www.lanana.org/docs/device-list/>\n/// - <http://www.lanana.org/docs/device-list/devices-2.6+.txt>\n#[derive(Copy, Clone)]\npub struct DeviceIDs {\n    pub major: u8,\n    pub minor: u8,\n}\n\n\n/// One of a file’s timestamps (created, accessed, or modified).\n#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]\npub struct Time {\n    pub seconds: time_t,\n    pub nanoseconds: time_t,\n}\n\n\n/// A file’s status in a Git repository. Whether a file is in a repository or\n/// not is handled by the Git module, rather than having a “null” variant in\n/// this enum.\n#[derive(PartialEq, Eq, Copy, Clone)]\npub enum GitStatus {\n\n    /// This file hasn’t changed since the last commit.\n    NotModified,\n\n    /// This file didn’t exist for the last commit, and is not specified in\n    /// the ignored files list.\n    New,\n\n    /// A file that’s been modified since the last commit.\n    Modified,\n\n    /// A deleted file. This can’t ever be shown, but it’s here anyway!\n    Deleted,\n\n    /// A file that Git has tracked a rename for.\n    Renamed,\n\n    /// A file that’s had its type (such as the file permissions) changed.\n    TypeChange,\n\n    /// A file that’s ignored (that matches a line in .gitignore)\n    Ignored,\n\n    /// A file that’s updated but unmerged.\n    Conflicted,\n}\n\n\n/// A file’s complete Git status. It’s possible to make changes to a file, add\n/// it to the staging area, then make *more* changes, so we need to list each\n/// file’s status for both of these.\n#[derive(Copy, Clone)]\npub struct Git {\n    pub staged:   GitStatus,\n    pub unstaged: GitStatus,\n}\n\nimpl Default for Git {\n\n    /// Create a Git status for a file with nothing done to it.\n    fn default() -> Self {\n        Self {\n            staged: GitStatus::NotModified,\n            unstaged: GitStatus::NotModified,\n        }\n    }\n}\n"
  },
  {
    "path": "src/fs/file.rs",
    "content": "//! Files, and methods and fields to access their metadata.\n\nuse std::io;\n#[cfg(unix)]\nuse std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};\n#[cfg(windows)]\nuse std::os::windows::fs::MetadataExt;\nuse std::path::{Path, PathBuf};\nuse std::time::{Duration, SystemTime, UNIX_EPOCH};\n\nuse log::*;\n\nuse crate::fs::dir::Dir;\nuse crate::fs::fields as f;\n\n\n/// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with\n/// associated data about the file.\n///\n/// Each file is definitely going to have its filename displayed at least\n/// once, have its file extension extracted at least once, and have its metadata\n/// information queried at least once, so it makes sense to do all this at the\n/// start and hold on to all the information.\npub struct File<'dir> {\n\n    /// The filename portion of this file’s path, including the extension.\n    ///\n    /// This is used to compare against certain filenames (such as checking if\n    /// it’s “Makefile” or something) and to highlight only the filename in\n    /// colour when displaying the path.\n    pub name: String,\n\n    /// The file’s name’s extension, if present, extracted from the name.\n    ///\n    /// This is queried many times over, so it’s worth caching it.\n    pub ext: Option<String>,\n\n    /// The path that begat this file.\n    ///\n    /// Even though the file’s name is extracted, the path needs to be kept\n    /// around, as certain operations involve looking up the file’s absolute\n    /// location (such as searching for compiled files) or using its original\n    /// path (following a symlink).\n    pub path: PathBuf,\n\n    /// A cached `metadata` (`stat`) call for this file.\n    ///\n    /// This too is queried multiple times, and is *not* cached by the OS, as\n    /// it could easily change between invocations — but exa is so short-lived\n    /// it’s better to just cache it.\n    pub metadata: std::fs::Metadata,\n\n    /// A reference to the directory that contains this file, if any.\n    ///\n    /// Filenames that get passed in on the command-line directly will have no\n    /// parent directory reference — although they technically have one on the\n    /// filesystem, we’ll never need to look at it, so it’ll be `None`.\n    /// However, *directories* that get passed in will produce files that\n    /// contain a reference to it, which is used in certain operations (such\n    /// as looking up compiled files).\n    pub parent_dir: Option<&'dir Dir>,\n\n    /// Whether this is one of the two `--all all` directories, `.` and `..`.\n    ///\n    /// Unlike all other entries, these are not returned as part of the\n    /// directory’s children, and are in fact added specifically by exa; this\n    /// means that they should be skipped when recursing.\n    pub is_all_all: bool,\n}\n\nimpl<'dir> File<'dir> {\n    pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN) -> io::Result<File<'dir>>\n    where PD: Into<Option<&'dir Dir>>,\n          FN: Into<Option<String>>\n    {\n        let parent_dir = parent_dir.into();\n        let name       = filename.into().unwrap_or_else(|| File::filename(&path));\n        let ext        = File::ext(&path);\n\n        debug!(\"Statting file {:?}\", &path);\n        let metadata   = std::fs::symlink_metadata(&path)?;\n        let is_all_all = false;\n\n        Ok(File { name, ext, path, metadata, parent_dir, is_all_all })\n    }\n\n    pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {\n        let path       = parent_dir.path.clone();\n        let ext        = File::ext(&path);\n\n        debug!(\"Statting file {:?}\", &path);\n        let metadata   = std::fs::symlink_metadata(&path)?;\n        let is_all_all = true;\n        let parent_dir = Some(parent_dir);\n\n        Ok(File { path, parent_dir, metadata, ext, name: \".\".into(), is_all_all })\n    }\n\n    pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {\n        let ext        = File::ext(&path);\n\n        debug!(\"Statting file {:?}\", &path);\n        let metadata   = std::fs::symlink_metadata(&path)?;\n        let is_all_all = true;\n        let parent_dir = Some(parent_dir);\n\n        Ok(File { path, parent_dir, metadata, ext, name: \"..\".into(), is_all_all })\n    }\n\n    /// A file’s name is derived from its string. This needs to handle directories\n    /// such as `/` or `..`, which have no `file_name` component. So instead, just\n    /// use the last component as the name.\n    pub fn filename(path: &Path) -> String {\n        if let Some(back) = path.components().next_back() {\n            back.as_os_str().to_string_lossy().to_string()\n        }\n        else {\n            // use the path as fallback\n            error!(\"Path {:?} has no last component\", path);\n            path.display().to_string()\n        }\n    }\n\n    /// Extract an extension from a file path, if one is present, in lowercase.\n    ///\n    /// The extension is the series of characters after the last dot. This\n    /// deliberately counts dotfiles, so the “.git” folder has the extension “git”.\n    ///\n    /// ASCII lowercasing is used because these extensions are only compared\n    /// against a pre-compiled list of extensions which are known to only exist\n    /// within ASCII, so it’s alright.\n    fn ext(path: &Path) -> Option<String> {\n        let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;\n\n        name.rfind('.')\n            .map(|p| name[p + 1 ..]\n            .to_ascii_lowercase())\n    }\n\n    /// Whether this file is a directory on the filesystem.\n    pub fn is_directory(&self) -> bool {\n        self.metadata.is_dir()\n    }\n\n    /// Whether this file is a directory, or a symlink pointing to a directory.\n    pub fn points_to_directory(&self) -> bool {\n        if self.is_directory() {\n            return true;\n        }\n\n        if self.is_link() {\n            let target = self.link_target();\n            if let FileTarget::Ok(target) = target {\n                return target.points_to_directory();\n            }\n        }\n\n        false\n    }\n\n    /// If this file is a directory on the filesystem, then clone its\n    /// `PathBuf` for use in one of our own `Dir` values, and read a list of\n    /// its contents.\n    ///\n    /// Returns an IO error upon failure, but this shouldn’t be used to check\n    /// if a `File` is a directory or not! For that, just use `is_directory()`.\n    pub fn to_dir(&self) -> io::Result<Dir> {\n        Dir::read_dir(self.path.clone())\n    }\n\n    /// Whether this file is a regular file on the filesystem — that is, not a\n    /// directory, a link, or anything else treated specially.\n    pub fn is_file(&self) -> bool {\n        self.metadata.is_file()\n    }\n\n    /// Whether this file is both a regular file *and* executable for the\n    /// current user. An executable file has a different purpose from an\n    /// executable directory, so they should be highlighted differently.\n    #[cfg(unix)]\n    pub fn is_executable_file(&self) -> bool {\n        let bit = modes::USER_EXECUTE;\n        self.is_file() && (self.metadata.permissions().mode() & bit) == bit\n    }\n\n    /// Whether this file is a symlink on the filesystem.\n    pub fn is_link(&self) -> bool {\n        self.metadata.file_type().is_symlink()\n    }\n\n    /// Whether this file is a named pipe on the filesystem.\n    #[cfg(unix)]\n    pub fn is_pipe(&self) -> bool {\n        self.metadata.file_type().is_fifo()\n    }\n\n    /// Whether this file is a char device on the filesystem.\n    #[cfg(unix)]\n    pub fn is_char_device(&self) -> bool {\n        self.metadata.file_type().is_char_device()\n    }\n\n    /// Whether this file is a block device on the filesystem.\n    #[cfg(unix)]\n    pub fn is_block_device(&self) -> bool {\n        self.metadata.file_type().is_block_device()\n    }\n\n    /// Whether this file is a socket on the filesystem.\n    #[cfg(unix)]\n    pub fn is_socket(&self) -> bool {\n        self.metadata.file_type().is_socket()\n    }\n\n\n    /// Re-prefixes the path pointed to by this file, if it’s a symlink, to\n    /// make it an absolute path that can be accessed from whichever\n    /// directory exa is being run from.\n    fn reorient_target_path(&self, path: &Path) -> PathBuf {\n        if path.is_absolute() {\n            path.to_path_buf()\n        }\n        else if let Some(dir) = self.parent_dir {\n            dir.join(path)\n        }\n        else if let Some(parent) = self.path.parent() {\n            parent.join(path)\n        }\n        else {\n            self.path.join(path)\n        }\n    }\n\n    /// Again assuming this file is a symlink, follows that link and returns\n    /// the result of following it.\n    ///\n    /// For a working symlink that the user is allowed to follow,\n    /// this will be the `File` object at the other end, which can then have\n    /// its name, colour, and other details read.\n    ///\n    /// For a broken symlink, returns where the file *would* be, if it\n    /// existed. If this file cannot be read at all, returns the error that\n    /// we got when we tried to read it.\n    pub fn link_target(&self) -> FileTarget<'dir> {\n\n        // We need to be careful to treat the path actually pointed to by\n        // this file — which could be absolute or relative — to the path\n        // we actually look up and turn into a `File` — which needs to be\n        // absolute to be accessible from any directory.\n        debug!(\"Reading link {:?}\", &self.path);\n        let path = match std::fs::read_link(&self.path) {\n            Ok(p)   => p,\n            Err(e)  => return FileTarget::Err(e),\n        };\n\n        let absolute_path = self.reorient_target_path(&path);\n\n        // Use plain `metadata` instead of `symlink_metadata` - we *want* to\n        // follow links.\n        match std::fs::metadata(&absolute_path) {\n            Ok(metadata) => {\n                let ext  = File::ext(&path);\n                let name = File::filename(&path);\n                let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };\n                FileTarget::Ok(Box::new(file))\n            }\n            Err(e) => {\n                error!(\"Error following link {:?}: {:#?}\", &path, e);\n                FileTarget::Broken(path)\n            }\n        }\n    }\n\n    /// This file’s number of hard links.\n    ///\n    /// It also reports whether this is both a regular file, and a file with\n    /// multiple links. This is important, because a file with multiple links\n    /// is uncommon, while you come across directories and other types\n    /// with multiple links much more often. Thus, it should get highlighted\n    /// more attentively.\n    #[cfg(unix)]\n    pub fn links(&self) -> f::Links {\n        let count = self.metadata.nlink();\n\n        f::Links {\n            count,\n            multiple: self.is_file() && count > 1,\n        }\n    }\n\n    /// This file’s inode.\n    #[cfg(unix)]\n    pub fn inode(&self) -> f::Inode {\n        f::Inode(self.metadata.ino())\n    }\n\n    /// This file’s number of filesystem blocks.\n    ///\n    /// (Not the size of each block, which we don’t actually report on)\n    #[cfg(unix)]\n    pub fn blocks(&self) -> f::Blocks {\n        if self.is_file() || self.is_link() {\n            f::Blocks::Some(self.metadata.blocks())\n        }\n        else {\n            f::Blocks::None\n        }\n    }\n\n    /// The ID of the user that own this file.\n    #[cfg(unix)]\n    pub fn user(&self) -> f::User {\n        f::User(self.metadata.uid())\n    }\n\n    /// The ID of the group that owns this file.\n    #[cfg(unix)]\n    pub fn group(&self) -> f::Group {\n        f::Group(self.metadata.gid())\n    }\n\n    /// This file’s size, if it’s a regular file.\n    ///\n    /// For directories, no size is given. Although they do have a size on\n    /// some filesystems, I’ve never looked at one of those numbers and gained\n    /// any information from it. So it’s going to be hidden instead.\n    ///\n    /// Block and character devices return their device IDs, because they\n    /// usually just have a file size of zero.\n    #[cfg(unix)]\n    pub fn size(&self) -> f::Size {\n        if self.is_directory() {\n            f::Size::None\n        }\n        else if self.is_char_device() || self.is_block_device() {\n            let device_ids = self.metadata.rdev().to_be_bytes();\n\n            // In C-land, getting the major and minor device IDs is done with\n            // preprocessor macros called `major` and `minor` that depend on\n            // the size of `dev_t`, but we just take the second-to-last and\n            // last bytes.\n            f::Size::DeviceIDs(f::DeviceIDs {\n                major: device_ids[6],\n                minor: device_ids[7],\n            })\n        }\n        else {\n            f::Size::Some(self.metadata.len())\n        }\n    }\n\n    #[cfg(windows)]\n    pub fn size(&self) -> f::Size {\n        if self.is_directory() {\n            f::Size::None\n        }\n        else {\n            f::Size::Some(self.metadata.len())\n        }\n    }\n\n    /// This file’s last modified timestamp, if available on this platform.\n    pub fn modified_time(&self) -> Option<SystemTime> {\n        self.metadata.modified().ok()\n    }\n\n    /// This file’s last changed timestamp, if available on this platform.\n    #[cfg(unix)]\n    pub fn changed_time(&self) -> Option<SystemTime> {\n        let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec());\n\n        if sec < 0 {\n            if nanosec > 0 {\n                sec += 1;\n                nanosec -= 1_000_000_000;\n            }\n\n            let duration = Duration::new(sec.unsigned_abs(), nanosec.unsigned_abs() as u32);\n            Some(UNIX_EPOCH - duration)\n        }\n        else {\n            let duration = Duration::new(sec as u64, nanosec as u32);\n            Some(UNIX_EPOCH + duration)\n        }\n    }\n\n    #[cfg(windows)]\n    pub fn changed_time(&self) -> Option<SystemTime> {\n        return self.modified_time()\n    }\n\n    /// This file’s last accessed timestamp, if available on this platform.\n    pub fn accessed_time(&self) -> Option<SystemTime> {\n        self.metadata.accessed().ok()\n    }\n\n    /// This file’s created timestamp, if available on this platform.\n    pub fn created_time(&self) -> Option<SystemTime> {\n        self.metadata.created().ok()\n    }\n\n    /// This file’s ‘type’.\n    ///\n    /// This is used a the leftmost character of the permissions column.\n    /// The file type can usually be guessed from the colour of the file, but\n    /// ls puts this character there.\n    #[cfg(unix)]\n    pub fn type_char(&self) -> f::Type {\n        if self.is_file() {\n            f::Type::File\n        }\n        else if self.is_directory() {\n            f::Type::Directory\n        }\n        else if self.is_pipe() {\n            f::Type::Pipe\n        }\n        else if self.is_link() {\n            f::Type::Link\n        }\n        else if self.is_char_device() {\n            f::Type::CharDevice\n        }\n        else if self.is_block_device() {\n            f::Type::BlockDevice\n        }\n        else if self.is_socket() {\n            f::Type::Socket\n        }\n        else {\n            f::Type::Special\n        }\n    }\n\n    #[cfg(windows)]\n    pub fn type_char(&self) -> f::Type {\n        if self.is_file() {\n            f::Type::File\n        }\n        else if self.is_directory() {\n            f::Type::Directory\n        }\n        else {\n            f::Type::Special\n        }\n    }\n\n    /// This file’s permissions, with flags for each bit.\n    #[cfg(unix)]\n    pub fn permissions(&self) -> f::Permissions {\n        let bits = self.metadata.mode();\n        let has_bit = |bit| bits & bit == bit;\n\n        f::Permissions {\n            user_read:      has_bit(modes::USER_READ),\n            user_write:     has_bit(modes::USER_WRITE),\n            user_execute:   has_bit(modes::USER_EXECUTE),\n\n            group_read:     has_bit(modes::GROUP_READ),\n            group_write:    has_bit(modes::GROUP_WRITE),\n            group_execute:  has_bit(modes::GROUP_EXECUTE),\n\n            other_read:     has_bit(modes::OTHER_READ),\n            other_write:    has_bit(modes::OTHER_WRITE),\n            other_execute:  has_bit(modes::OTHER_EXECUTE),\n\n            sticky:         has_bit(modes::STICKY),\n            setgid:         has_bit(modes::SETGID),\n            setuid:         has_bit(modes::SETUID),\n        }\n    }\n\n    #[cfg(windows)]\n    pub fn attributes(&self) -> f::Attributes {\n        let bits = self.metadata.file_attributes();\n        let has_bit = |bit| bits & bit == bit;\n\n        // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants\n        f::Attributes {\n            directory:      has_bit(0x10),\n            archive:        has_bit(0x20),\n            readonly:       has_bit(0x1),\n            hidden:         has_bit(0x2),\n            system:         has_bit(0x4),\n            reparse_point:  has_bit(0x400),\n        }\n    }\n\n    /// Whether this file’s extension is any of the strings that get passed in.\n    ///\n    /// This will always return `false` if the file has no extension.\n    pub fn extension_is_one_of(&self, choices: &[&str]) -> bool {\n        match &self.ext {\n            Some(ext)  => choices.contains(&&ext[..]),\n            None       => false,\n        }\n    }\n\n    /// Whether this file’s name, including extension, is any of the strings\n    /// that get passed in.\n    pub fn name_is_one_of(&self, choices: &[&str]) -> bool {\n        choices.contains(&&self.name[..])\n    }\n}\n\n\nimpl<'a> AsRef<File<'a>> for File<'a> {\n    fn as_ref(&self) -> &File<'a> {\n        self\n    }\n}\n\n\n/// The result of following a symlink.\npub enum FileTarget<'dir> {\n\n    /// The symlink pointed at a file that exists.\n    Ok(Box<File<'dir>>),\n\n    /// The symlink pointed at a file that does not exist. Holds the path\n    /// where the file would be, if it existed.\n    Broken(PathBuf),\n\n    /// There was an IO error when following the link. This can happen if the\n    /// file isn’t a link to begin with, but also if, say, we don’t have\n    /// permission to follow it.\n    Err(io::Error),\n\n    // Err is its own variant, instead of having the whole thing be inside an\n    // `io::Result`, because being unable to follow a symlink is not a serious\n    // error — we just display the error message and move on.\n}\n\nimpl<'dir> FileTarget<'dir> {\n\n    /// Whether this link doesn’t lead to a file, for whatever reason. This\n    /// gets used to determine how to highlight the link in grid views.\n    pub fn is_broken(&self) -> bool {\n        matches!(self, Self::Broken(_) | Self::Err(_))\n    }\n}\n\n\n/// More readable aliases for the permission bits exposed by libc.\n#[allow(trivial_numeric_casts)]\n#[cfg(unix)]\nmod modes {\n\n    // The `libc::mode_t` type’s actual type varies, but the value returned\n    // from `metadata.permissions().mode()` is always `u32`.\n    pub type Mode = u32;\n\n    pub const USER_READ: Mode     = libc::S_IRUSR as Mode;\n    pub const USER_WRITE: Mode    = libc::S_IWUSR as Mode;\n    pub const USER_EXECUTE: Mode  = libc::S_IXUSR as Mode;\n\n    pub const GROUP_READ: Mode    = libc::S_IRGRP as Mode;\n    pub const GROUP_WRITE: Mode   = libc::S_IWGRP as Mode;\n    pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;\n\n    pub const OTHER_READ: Mode    = libc::S_IROTH as Mode;\n    pub const OTHER_WRITE: Mode   = libc::S_IWOTH as Mode;\n    pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;\n\n    pub const STICKY: Mode        = libc::S_ISVTX as Mode;\n    pub const SETGID: Mode        = libc::S_ISGID as Mode;\n    pub const SETUID: Mode        = libc::S_ISUID as Mode;\n}\n\n\n#[cfg(test)]\nmod ext_test {\n    use super::File;\n    use std::path::Path;\n\n    #[test]\n    fn extension() {\n        assert_eq!(Some(\"dat\".to_string()), File::ext(Path::new(\"fester.dat\")))\n    }\n\n    #[test]\n    fn dotfile() {\n        assert_eq!(Some(\"vimrc\".to_string()), File::ext(Path::new(\".vimrc\")))\n    }\n\n    #[test]\n    fn no_extension() {\n        assert_eq!(None, File::ext(Path::new(\"jarlsberg\")))\n    }\n}\n\n\n#[cfg(test)]\nmod filename_test {\n    use super::File;\n    use std::path::Path;\n\n    #[test]\n    fn file() {\n        assert_eq!(\"fester.dat\", File::filename(Path::new(\"fester.dat\")))\n    }\n\n    #[test]\n    fn no_path() {\n        assert_eq!(\"foo.wha\", File::filename(Path::new(\"/var/cache/foo.wha\")))\n    }\n\n    #[test]\n    fn here() {\n        assert_eq!(\".\", File::filename(Path::new(\".\")))\n    }\n\n    #[test]\n    fn there() {\n        assert_eq!(\"..\", File::filename(Path::new(\"..\")))\n    }\n\n    #[test]\n    fn everywhere() {\n        assert_eq!(\"..\", File::filename(Path::new(\"./..\")))\n    }\n\n    #[test]\n    #[cfg(unix)]\n    fn topmost() {\n        assert_eq!(\"/\", File::filename(Path::new(\"/\")))\n    }\n}\n"
  },
  {
    "path": "src/fs/filter.rs",
    "content": "//! Filtering and sorting the list of files before displaying them.\n\nuse std::cmp::Ordering;\nuse std::iter::FromIterator;\n#[cfg(unix)]\nuse std::os::unix::fs::MetadataExt;\n\nuse crate::fs::DotFilter;\nuse crate::fs::File;\n\n\n/// The **file filter** processes a list of files before displaying them to\n/// the user, by removing files they don’t want to see, and putting the list\n/// in the desired order.\n///\n/// Usually a user does not want to see *every* file in the list. The most\n/// common case is to remove files starting with `.`, which are designated\n/// as ‘hidden’ files.\n///\n/// The special files `.` and `..` files are not actually filtered out, but\n/// need to be inserted into the list, in a special case.\n///\n/// The filter also governs sorting the list. After being filtered, pairs of\n/// files are compared and sorted based on the result, with the sort field\n/// performing the comparison.\n#[derive(PartialEq, Eq, Debug, Clone)]\npub struct FileFilter {\n\n    /// Whether directories should be listed first, and other types of file\n    /// second. Some users prefer it like this.\n    pub list_dirs_first: bool,\n\n    /// The metadata field to sort by.\n    pub sort_field: SortField,\n\n    /// Whether to reverse the sorting order. This would sort the largest\n    /// files first, or files starting with Z, or the most-recently-changed\n    /// ones, depending on the sort field.\n    pub reverse: bool,\n\n    /// Whether to only show directories.\n    pub only_dirs: bool,\n\n    /// Which invisible “dot” files to include when listing a directory.\n    ///\n    /// Files starting with a single “.” are used to determine “system” or\n    /// “configuration” files that should not be displayed in a regular\n    /// directory listing, and the directory entries “.” and “..” are\n    /// considered extra-special.\n    ///\n    /// This came about more or less by a complete historical accident,\n    /// when the original `ls` tried to hide `.` and `..`:\n    ///\n    /// [Linux History: How Dot Files Became Hidden Files](https://linux-audit.com/linux-history-how-dot-files-became-hidden-files/)\n    pub dot_filter: DotFilter,\n\n    /// Glob patterns to ignore. Any file name that matches *any* of these\n    /// patterns won’t be displayed in the list.\n    pub ignore_patterns: IgnorePatterns,\n\n    /// Whether to ignore Git-ignored patterns.\n    pub git_ignore: GitIgnore,\n}\n\nimpl FileFilter {\n    /// Remove every file in the given vector that does *not* pass the\n    /// filter predicate for files found inside a directory.\n    pub fn filter_child_files(&self, files: &mut Vec<File<'_>>) {\n        files.retain(|f| ! self.ignore_patterns.is_ignored(&f.name));\n\n        if self.only_dirs {\n            files.retain(File::is_directory);\n        }\n    }\n\n    /// Remove every file in the given vector that does *not* pass the\n    /// filter predicate for file names specified on the command-line.\n    ///\n    /// The rules are different for these types of files than the other\n    /// type because the ignore rules can be used with globbing. For\n    /// example, running `exa -I='*.tmp' .vimrc` shouldn’t filter out the\n    /// dotfile, because it’s been directly specified. But running\n    /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained\n    /// from the glob, even though the globbing is done by the shell!\n    pub fn filter_argument_files(&self, files: &mut Vec<File<'_>>) {\n        files.retain(|f| {\n            ! self.ignore_patterns.is_ignored(&f.name)\n        });\n    }\n\n    /// Sort the files in the given vector based on the sort field option.\n    pub fn sort_files<'a, F>(&self, files: &mut [F])\n    where F: AsRef<File<'a>>\n    {\n        files.sort_by(|a, b| {\n            self.sort_field.compare_files(a.as_ref(), b.as_ref())\n        });\n\n        if self.reverse {\n            files.reverse();\n        }\n\n        if self.list_dirs_first {\n            // This relies on the fact that `sort_by` is *stable*: it will keep\n            // adjacent elements next to each other.\n            files.sort_by(|a, b| {\n                b.as_ref().points_to_directory()\n                    .cmp(&a.as_ref().points_to_directory())\n            });\n        }\n    }\n}\n\n\n/// User-supplied field to sort by.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum SortField {\n\n    /// Don’t apply any sorting. This is usually used as an optimisation in\n    /// scripts, where the order doesn’t matter.\n    Unsorted,\n\n    /// The file name. This is the default sorting.\n    Name(SortCase),\n\n    /// The file’s extension, with extensionless files being listed first.\n    Extension(SortCase),\n\n    /// The file’s size, in bytes.\n    Size,\n\n    /// The file’s inode, which usually corresponds to the order in which\n    /// files were created on the filesystem, more or less.\n    #[cfg(unix)]\n    FileInode,\n\n    /// The time the file was modified (the “mtime”).\n    ///\n    /// As this is stored as a Unix timestamp, rather than a local time\n    /// instance, the time zone does not matter and will only be used to\n    /// display the timestamps, not compare them.\n    ModifiedDate,\n\n    /// The time the file was accessed (the “atime”).\n    ///\n    /// Oddly enough, this field rarely holds the *actual* accessed time.\n    /// Recording a read time means writing to the file each time it’s read\n    /// slows the whole operation down, so many systems will only update the\n    /// timestamp in certain circumstances. This has become common enough that\n    /// it’s now expected behaviour!\n    /// <https://unix.stackexchange.com/a/8842>\n    AccessedDate,\n\n    /// The time the file was changed (the “ctime”).\n    ///\n    /// This field is used to mark the time when a file’s metadata\n    /// changed — its permissions, owners, or link count.\n    ///\n    /// In original Unix, this was, however, meant as creation time.\n    /// <https://www.bell-labs.com/usr/dmr/www/cacm.html>\n    ChangedDate,\n\n    /// The time the file was created (the “btime” or “birthtime”).\n    CreatedDate,\n\n    /// The type of the file: directories, links, pipes, regular, files, etc.\n    ///\n    /// Files are ordered according to the `PartialOrd` implementation of\n    /// `fs::fields::Type`, so changing that will change this.\n    FileType,\n\n    /// The “age” of the file, which is the time it was modified sorted\n    /// backwards. The reverse of the `ModifiedDate` ordering!\n    ///\n    /// It turns out that listing the most-recently-modified files first is a\n    /// common-enough use case that it deserves its own variant. This would be\n    /// implemented by just using the modified date and setting the reverse\n    /// flag, but this would make reversing *that* output not work, which is\n    /// bad, even though that’s kind of nonsensical. So it’s its own variant\n    /// that can be reversed like usual.\n    ModifiedAge,\n\n    /// The file's name, however if the name of the file begins with `.`\n    /// ignore the leading `.` and then sort as Name\n    NameMixHidden(SortCase),\n}\n\n/// Whether a field should be sorted case-sensitively or case-insensitively.\n/// This determines which of the `natord` functions to use.\n///\n/// I kept on forgetting which one was sensitive and which one was\n/// insensitive. Would a case-sensitive sort put capital letters first because\n/// it takes the case of the letters into account, or intermingle them with\n/// lowercase letters because it takes the difference between the two cases\n/// into account? I gave up and just named these two variants after the\n/// effects they have.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum SortCase {\n\n    /// Sort files case-sensitively with uppercase first, with ‘A’ coming\n    /// before ‘a’.\n    ABCabc,\n\n    /// Sort files case-insensitively, with ‘A’ being equal to ‘a’.\n    AaBbCc,\n}\n\nimpl SortField {\n\n    /// Compares two files to determine the order they should be listed in,\n    /// depending on the search field.\n    ///\n    /// The `natord` crate is used here to provide a more *natural* sorting\n    /// order than just sorting character-by-character. This splits filenames\n    /// into groups between letters and numbers, and then sorts those blocks\n    /// together, so `file10` will sort after `file9`, instead of before it\n    /// because of the `1`.\n    pub fn compare_files(self, a: &File<'_>, b: &File<'_>) -> Ordering {\n        use self::SortCase::{ABCabc, AaBbCc};\n\n        match self {\n            Self::Unsorted  => Ordering::Equal,\n\n            Self::Name(ABCabc)  => natord::compare(&a.name, &b.name),\n            Self::Name(AaBbCc)  => natord::compare_ignore_case(&a.name, &b.name),\n\n            Self::Size          => a.metadata.len().cmp(&b.metadata.len()),\n            #[cfg(unix)]\n            Self::FileInode     => a.metadata.ino().cmp(&b.metadata.ino()),\n            Self::ModifiedDate  => a.modified_time().cmp(&b.modified_time()),\n            Self::AccessedDate  => a.accessed_time().cmp(&b.accessed_time()),\n            Self::ChangedDate   => a.changed_time().cmp(&b.changed_time()),\n            Self::CreatedDate   => a.created_time().cmp(&b.created_time()),\n            Self::ModifiedAge   => b.modified_time().cmp(&a.modified_time()),  // flip b and a\n\n            Self::FileType => match a.type_char().cmp(&b.type_char()) { // todo: this recomputes\n                Ordering::Equal  => natord::compare(&*a.name, &*b.name),\n                order            => order,\n            },\n\n            Self::Extension(ABCabc) => match a.ext.cmp(&b.ext) {\n                Ordering::Equal  => natord::compare(&*a.name, &*b.name),\n                order            => order,\n            },\n\n            Self::Extension(AaBbCc) => match a.ext.cmp(&b.ext) {\n                Ordering::Equal  => natord::compare_ignore_case(&*a.name, &*b.name),\n                order            => order,\n            },\n\n            Self::NameMixHidden(ABCabc) => natord::compare(\n                Self::strip_dot(&a.name),\n                Self::strip_dot(&b.name)\n            ),\n            Self::NameMixHidden(AaBbCc) => natord::compare_ignore_case(\n                Self::strip_dot(&a.name),\n                Self::strip_dot(&b.name)\n            )\n        }\n    }\n\n    fn strip_dot(n: &str) -> &str {\n        match n.strip_prefix('.') {\n            Some(s) => s,\n            None    => n,\n        }\n    }\n}\n\n\n/// The **ignore patterns** are a list of globs that are tested against\n/// each filename, and if any of them match, that file isn’t displayed.\n/// This lets a user hide, say, text files by ignoring `*.txt`.\n#[derive(PartialEq, Eq, Default, Debug, Clone)]\npub struct IgnorePatterns {\n    patterns: Vec<glob::Pattern>,\n}\n\nimpl FromIterator<glob::Pattern> for IgnorePatterns {\n\n    fn from_iter<I>(iter: I) -> Self\n    where I: IntoIterator<Item = glob::Pattern>\n    {\n        let patterns = iter.into_iter().collect();\n        Self { patterns }\n    }\n}\n\nimpl IgnorePatterns {\n\n    /// Create a new list from the input glob strings, turning the inputs that\n    /// are valid glob patterns into an `IgnorePatterns`. The inputs that\n    /// don’t parse correctly are returned separately.\n    pub fn parse_from_iter<'a, I: IntoIterator<Item = &'a str>>(iter: I) -> (Self, Vec<glob::PatternError>) {\n        let iter = iter.into_iter();\n\n        // Almost all glob patterns are valid, so it’s worth pre-allocating\n        // the vector with enough space for all of them.\n        let mut patterns = match iter.size_hint() {\n            (_, Some(count))  => Vec::with_capacity(count),\n             _                => Vec::new(),\n        };\n\n        // Similarly, assume there won’t be any errors.\n        let mut errors = Vec::new();\n\n        for input in iter {\n            match glob::Pattern::new(input) {\n                Ok(pat) => patterns.push(pat),\n                Err(e)  => errors.push(e),\n            }\n        }\n\n        (Self { patterns }, errors)\n    }\n\n    /// Create a new empty set of patterns that matches nothing.\n    pub fn empty() -> Self {\n        Self { patterns: Vec::new() }\n    }\n\n    /// Test whether the given file should be hidden from the results.\n    fn is_ignored(&self, file: &str) -> bool {\n        self.patterns.iter().any(|p| p.matches(file))\n    }\n}\n\n\n/// Whether to ignore or display files that Git would ignore.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum GitIgnore {\n\n    /// Ignore files that Git would ignore.\n    CheckAndIgnore,\n\n    /// Display files, even if Git would ignore them.\n    Off,\n}\n\n\n\n#[cfg(test)]\nmod test_ignores {\n    use super::*;\n\n    #[test]\n    fn empty_matches_nothing() {\n        let pats = IgnorePatterns::empty();\n        assert!(!pats.is_ignored(\"nothing\"));\n        assert!(!pats.is_ignored(\"test.mp3\"));\n    }\n\n    #[test]\n    fn ignores_a_glob() {\n        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ \"*.mp3\" ]);\n        assert!(fails.is_empty());\n        assert!(!pats.is_ignored(\"nothing\"));\n        assert!(pats.is_ignored(\"test.mp3\"));\n    }\n\n    #[test]\n    fn ignores_an_exact_filename() {\n        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ \"nothing\" ]);\n        assert!(fails.is_empty());\n        assert!(pats.is_ignored(\"nothing\"));\n        assert!(!pats.is_ignored(\"test.mp3\"));\n    }\n\n    #[test]\n    fn ignores_both() {\n        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ \"nothing\", \"*.mp3\" ]);\n        assert!(fails.is_empty());\n        assert!(pats.is_ignored(\"nothing\"));\n        assert!(pats.is_ignored(\"test.mp3\"));\n    }\n}\n"
  },
  {
    "path": "src/fs/mod.rs",
    "content": "mod dir;\npub use self::dir::{Dir, DotFilter};\n\nmod file;\npub use self::file::{File, FileTarget};\n\npub mod dir_action;\npub mod feature;\npub mod fields;\npub mod filter;\n"
  },
  {
    "path": "src/info/filetype.rs",
    "content": "//! Tests for various types of file (video, image, compressed, etc).\n//!\n//! Currently this is dependent on the file’s name and extension, because\n//! those are the only metadata that we have access to without reading the\n//! file’s contents.\n\nuse ansi_term::Style;\n\nuse crate::fs::File;\nuse crate::output::icons::FileIcon;\nuse crate::theme::FileColours;\n\n\n#[derive(Debug, Default, PartialEq, Eq)]\npub struct FileExtensions;\n\nimpl FileExtensions {\n\n    /// An “immediate” file is something that can be run or activated somehow\n    /// in order to kick off the build of a project. It’s usually only present\n    /// in directories full of source code.\n    #[allow(clippy::case_sensitive_file_extension_comparisons)]\n    fn is_immediate(&self, file: &File<'_>) -> bool {\n        file.name.to_lowercase().starts_with(\"readme\") ||\n        file.name.ends_with(\".ninja\") ||\n        file.name_is_one_of( &[\n            \"Makefile\", \"Cargo.toml\", \"SConstruct\", \"CMakeLists.txt\",\n            \"build.gradle\", \"pom.xml\", \"Rakefile\", \"package.json\", \"Gruntfile.js\",\n            \"Gruntfile.coffee\", \"BUILD\", \"BUILD.bazel\", \"WORKSPACE\", \"build.xml\", \"Podfile\",\n            \"webpack.config.js\", \"meson.build\", \"composer.json\", \"RoboFile.php\", \"PKGBUILD\",\n            \"Justfile\", \"Procfile\", \"Dockerfile\", \"Containerfile\", \"Vagrantfile\", \"Brewfile\",\n            \"Gemfile\", \"Pipfile\", \"build.sbt\", \"mix.exs\", \"bsconfig.json\", \"tsconfig.json\",\n        ])\n    }\n\n    fn is_image(&self, file: &File<'_>) -> bool {\n        file.extension_is_one_of( &[\n            \"png\", \"jfi\", \"jfif\", \"jif\", \"jpe\", \"jpeg\", \"jpg\", \"gif\", \"bmp\",\n            \"tiff\", \"tif\", \"ppm\", \"pgm\", \"pbm\", \"pnm\", \"webp\", \"raw\", \"arw\",\n            \"svg\", \"stl\", \"eps\", \"dvi\", \"ps\", \"cbr\", \"jpf\", \"cbz\", \"xpm\",\n            \"ico\", \"cr2\", \"orf\", \"nef\", \"heif\", \"avif\", \"jxl\", \"j2k\", \"jp2\",\n            \"j2c\", \"jpx\",\n        ])\n    }\n\n    fn is_video(&self, file: &File<'_>) -> bool {\n        file.extension_is_one_of( &[\n            \"avi\", \"flv\", \"m2v\", \"m4v\", \"mkv\", \"mov\", \"mp4\", \"mpeg\",\n            \"mpg\", \"ogm\", \"ogv\", \"vob\", \"wmv\", \"webm\", \"m2ts\", \"heic\",\n        ])\n    }\n\n    fn is_music(&self, file: &File<'_>) -> bool {\n        file.extension_is_one_of( &[\n            \"aac\", \"m4a\", \"mp3\", \"ogg\", \"wma\", \"mka\", \"opus\",\n        ])\n    }\n\n    // Lossless music, rather than any other kind of data...\n    fn is_lossless(&self, file: &File<'_>) -> bool {\n        file.extension_is_one_of( &[\n            \"alac\", \"ape\", \"flac\", \"wav\",\n        ])\n    }\n\n    fn is_crypto(&self, file: &File<'_>) -> bool {\n        file.extension_is_one_of( &[\n            \"asc\", \"enc\", \"gpg\", \"pgp\", \"sig\", \"signature\", \"pfx\", \"p12\",\n        ])\n    }\n\n    fn is_document(&self, file: &File<'_>) -> bool {\n        file.extension_is_one_of( &[\n            \"djvu\", \"doc\", \"docx\", \"dvi\", \"eml\", \"eps\", \"fotd\", \"key\",\n            \"keynote\", \"numbers\", \"odp\", \"odt\", \"pages\", \"pdf\", \"ppt\",\n            \"pptx\", \"rtf\", \"xls\", \"xlsx\",\n        ])\n    }\n\n    fn is_compressed(&self, file: &File<'_>) -> bool {\n        file.extension_is_one_of( &[\n            \"zip\", \"tar\", \"Z\", \"z\", \"gz\", \"bz2\", \"a\", \"ar\", \"7z\",\n            \"iso\", \"dmg\", \"tc\", \"rar\", \"par\", \"tgz\", \"xz\", \"txz\",\n            \"lz\", \"tlz\", \"lzma\", \"deb\", \"rpm\", \"zst\", \"lz4\", \"cpio\",\n        ])\n    }\n\n    fn is_temp(&self, file: &File<'_>) -> bool {\n        file.name.ends_with('~')\n            || (file.name.starts_with('#') && file.name.ends_with('#'))\n            || file.extension_is_one_of( &[ \"tmp\", \"swp\", \"swo\", \"swn\", \"bak\", \"bkp\", \"bk\" ])\n    }\n\n    fn is_compiled(&self, file: &File<'_>) -> bool {\n        if file.extension_is_one_of( &[ \"class\", \"elc\", \"hi\", \"o\", \"pyc\", \"zwc\", \"ko\" ]) {\n            true\n        }\n        else if let Some(dir) = file.parent_dir {\n            file.get_source_files().iter().any(|path| dir.contains(path))\n        }\n        else {\n            false\n        }\n    }\n}\n\nimpl FileColours for FileExtensions {\n    fn colour_file(&self, file: &File<'_>) -> Option<Style> {\n        use ansi_term::Colour::*;\n\n        Some(match file {\n            f if self.is_temp(f)        => Fixed(244).normal(),\n            f if self.is_immediate(f)   => Yellow.bold().underline(),\n            f if self.is_image(f)       => Fixed(133).normal(),\n            f if self.is_video(f)       => Fixed(135).normal(),\n            f if self.is_music(f)       => Fixed(92).normal(),\n            f if self.is_lossless(f)    => Fixed(93).normal(),\n            f if self.is_crypto(f)      => Fixed(109).normal(),\n            f if self.is_document(f)    => Fixed(105).normal(),\n            f if self.is_compressed(f)  => Red.normal(),\n            f if self.is_compiled(f)    => Fixed(137).normal(),\n            _                           => return None,\n        })\n    }\n}\n\nimpl FileIcon for FileExtensions {\n    fn icon_file(&self, file: &File<'_>) -> Option<char> {\n        use crate::output::icons::Icons;\n\n        if self.is_music(file) || self.is_lossless(file) {\n            Some(Icons::Audio.value())\n        }\n        else if self.is_image(file) {\n            Some(Icons::Image.value())\n        }\n        else if self.is_video(file) {\n            Some(Icons::Video.value())\n        }\n        else {\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "src/info/mod.rs",
    "content": "//! The “info” module contains routines that aren’t about probing the\n//! filesystem nor displaying output to the user, but are internal “business\n//! logic” routines that are performed on a file’s already-read metadata.\n//! (This counts the file name as metadata.)\n\npub mod filetype;\nmod sources;\n"
  },
  {
    "path": "src/info/sources.rs",
    "content": "use std::path::PathBuf;\n\nuse crate::fs::File;\n\n\nimpl<'a> File<'a> {\n\n    /// For this file, return a vector of alternate file paths that, if any of\n    /// them exist, mean that *this* file should be coloured as “compiled”.\n    ///\n    /// The point of this is to highlight compiled files such as `foo.js` when\n    /// their source file `foo.coffee` exists in the same directory.\n    /// For example, `foo.js` is perfectly valid without `foo.coffee`, so we\n    /// don’t want to always blindly highlight `*.js` as compiled.\n    /// (See also `FileExtensions#is_compiled`)\n    pub fn get_source_files(&self) -> Vec<PathBuf> {\n        if let Some(ext) = &self.ext {\n            match &ext[..] {\n                \"css\"   => vec![self.path.with_extension(\"sass\"), self.path.with_extension(\"scss\"),  // SASS, SCSS\n                                self.path.with_extension(\"styl\"), self.path.with_extension(\"less\")],  // Stylus, Less\n                \"js\"    => vec![self.path.with_extension(\"coffee\"), self.path.with_extension(\"ts\")],  // CoffeeScript, TypeScript\n\n                \"aux\" |                                          // TeX: auxiliary file\n                \"bbl\" |                                          // BibTeX bibliography file\n                \"bcf\" |                                          // biblatex control file\n                \"blg\" |                                          // BibTeX log file\n                \"fdb_latexmk\" |                                  // TeX latexmk file\n                \"fls\" |                                          // TeX -recorder file\n                \"lof\" |                                          // TeX list of figures\n                \"log\" |                                          // TeX log file\n                \"lot\" |                                          // TeX list of tables\n                \"toc\" => vec![self.path.with_extension(\"tex\")],  // TeX table of contents\n\n                _ => vec![],  // No source files if none of the above\n            }\n        }\n        else {\n            vec![]  // No source files if there’s no extension, either!\n        }\n    }\n}\n"
  },
  {
    "path": "src/logger.rs",
    "content": "//! Debug error logging.\n\nuse std::ffi::OsStr;\n\nuse ansi_term::{Colour, ANSIString};\n\n\n/// Sets the internal logger, changing the log level based on the value of an\n/// environment variable.\npub fn configure<T: AsRef<OsStr>>(ev: Option<T>) {\n    let ev = match ev {\n        Some(v)  => v,\n        None     => return,\n    };\n\n    let env_var = ev.as_ref();\n    if env_var.is_empty() {\n        return;\n    }\n\n    if env_var == \"trace\" {\n        log::set_max_level(log::LevelFilter::Trace);\n    }\n    else {\n        log::set_max_level(log::LevelFilter::Debug);\n    }\n\n    let result = log::set_logger(GLOBAL_LOGGER);\n    if let Err(e) = result {\n        eprintln!(\"Failed to initialise logger: {}\", e);\n    }\n}\n\n\n#[derive(Debug)]\nstruct Logger;\n\nconst GLOBAL_LOGGER: &Logger = &Logger;\n\nimpl log::Log for Logger {\n    fn enabled(&self, _: &log::Metadata<'_>) -> bool {\n        true  // no need to filter after using ‘set_max_level’.\n    }\n\n    fn log(&self, record: &log::Record<'_>) {\n        let open = Colour::Fixed(243).paint(\"[\");\n        let level = level(record.level());\n        let close = Colour::Fixed(243).paint(\"]\");\n\n        eprintln!(\"{}{} {}{} {}\", open, level, record.target(), close, record.args());\n    }\n\n    fn flush(&self) {\n        // no need to flush with ‘eprintln!’.\n    }\n}\n\nfn level(level: log::Level) -> ANSIString<'static> {\n    match level {\n        log::Level::Error  => Colour::Red.paint(\"ERROR\"),\n        log::Level::Warn   => Colour::Yellow.paint(\"WARN\"),\n        log::Level::Info   => Colour::Cyan.paint(\"INFO\"),\n        log::Level::Debug  => Colour::Blue.paint(\"DEBUG\"),\n        log::Level::Trace  => Colour::Fixed(245).paint(\"TRACE\"),\n    }\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "#![warn(deprecated_in_future)]\n#![warn(future_incompatible)]\n#![warn(nonstandard_style)]\n#![warn(rust_2018_compatibility)]\n#![warn(rust_2018_idioms)]\n#![warn(trivial_casts, trivial_numeric_casts)]\n#![warn(unused)]\n\n#![warn(clippy::all, clippy::pedantic)]\n#![allow(clippy::cast_precision_loss)]\n#![allow(clippy::cast_possible_truncation)]\n#![allow(clippy::cast_possible_wrap)]\n#![allow(clippy::cast_sign_loss)]\n#![allow(clippy::enum_glob_use)]\n#![allow(clippy::map_unwrap_or)]\n#![allow(clippy::match_same_arms)]\n#![allow(clippy::module_name_repetitions)]\n#![allow(clippy::non_ascii_literal)]\n#![allow(clippy::option_if_let_else)]\n#![allow(clippy::too_many_lines)]\n#![allow(clippy::unused_self)]\n#![allow(clippy::upper_case_acronyms)]\n#![allow(clippy::wildcard_imports)]\n\nuse std::env;\nuse std::ffi::{OsStr, OsString};\nuse std::io::{self, Write, ErrorKind};\nuse std::path::{Component, PathBuf};\n\nuse ansi_term::{ANSIStrings, Style};\n\nuse log::*;\n\nuse crate::fs::{Dir, File};\nuse crate::fs::feature::git::GitCache;\nuse crate::fs::filter::GitIgnore;\nuse crate::options::{Options, Vars, vars, OptionsResult};\nuse crate::output::{escape, lines, grid, grid_details, details, View, Mode};\nuse crate::theme::Theme;\n\nmod fs;\nmod info;\nmod logger;\nmod options;\nmod output;\nmod theme;\n\n\nfn main() {\n    use std::process::exit;\n\n    #[cfg(unix)]\n    unsafe {\n        libc::signal(libc::SIGPIPE, libc::SIG_DFL);\n    }\n\n    logger::configure(env::var_os(vars::EXA_DEBUG));\n\n    #[cfg(windows)]\n    if let Err(e) = ansi_term::enable_ansi_support() {\n        warn!(\"Failed to enable ANSI support: {}\", e);\n    }\n\n    let args: Vec<_> = env::args_os().skip(1).collect();\n    match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) {\n        OptionsResult::Ok(options, mut input_paths) => {\n\n            // List the current directory by default.\n            // (This has to be done here, otherwise git_options won’t see it.)\n            if input_paths.is_empty() {\n                input_paths = vec![ OsStr::new(\".\") ];\n            }\n\n            let git = git_options(&options, &input_paths);\n            let writer = io::stdout();\n\n            let console_width = options.view.width.actual_terminal_width();\n            let theme = options.theme.to_theme(console_width.is_some());\n            let exa = Exa { options, writer, input_paths, theme, console_width, git };\n\n            match exa.run() {\n                Ok(exit_status) => {\n                    exit(exit_status);\n                }\n\n                Err(e) if e.kind() == ErrorKind::BrokenPipe => {\n                    warn!(\"Broken pipe error: {}\", e);\n                    exit(exits::SUCCESS);\n                }\n\n                Err(e) => {\n                    eprintln!(\"{}\", e);\n                    exit(exits::RUNTIME_ERROR);\n                }\n            }\n        }\n\n        OptionsResult::Help(help_text) => {\n            print!(\"{}\", help_text);\n        }\n\n        OptionsResult::Version(version_str) => {\n            print!(\"{}\", version_str);\n        }\n\n        OptionsResult::InvalidOptions(error) => {\n            eprintln!(\"exa: {}\", error);\n\n            if let Some(s) = error.suggestion() {\n                eprintln!(\"{}\", s);\n            }\n\n            exit(exits::OPTIONS_ERROR);\n        }\n    }\n}\n\n\n/// The main program wrapper.\npub struct Exa<'args> {\n\n    /// List of command-line options, having been successfully parsed.\n    pub options: Options,\n\n    /// The output handle that we write to.\n    pub writer: io::Stdout,\n\n    /// List of the free command-line arguments that should correspond to file\n    /// names (anything that isn’t an option).\n    pub input_paths: Vec<&'args OsStr>,\n\n    /// The theme that has been configured from the command-line options and\n    /// environment variables. If colours are disabled, this is a theme with\n    /// every style set to the default.\n    pub theme: Theme,\n\n    /// The detected width of the console. This is used to determine which\n    /// view to use.\n    pub console_width: Option<usize>,\n\n    /// A global Git cache, if the option was passed in.\n    /// This has to last the lifetime of the program, because the user might\n    /// want to list several directories in the same repository.\n    pub git: Option<GitCache>,\n}\n\n/// The “real” environment variables type.\n/// Instead of just calling `var_os` from within the options module,\n/// the method of looking up environment variables has to be passed in.\nstruct LiveVars;\nimpl Vars for LiveVars {\n    fn get(&self, name: &'static str) -> Option<OsString> {\n        env::var_os(name)\n    }\n}\n\n/// Create a Git cache populated with the arguments that are going to be\n/// listed before they’re actually listed, if the options demand it.\nfn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {\n    if options.should_scan_for_git() {\n        Some(args.iter().map(PathBuf::from).collect())\n    }\n    else {\n        None\n    }\n}\n\nimpl<'args> Exa<'args> {\n    /// # Errors\n    ///\n    /// Will return `Err` if printing to stderr fails.\n    pub fn run(mut self) -> io::Result<i32> {\n        debug!(\"Running with options: {:#?}\", self.options);\n\n        let mut files = Vec::new();\n        let mut dirs = Vec::new();\n        let mut exit_status = 0;\n\n        for file_path in &self.input_paths {\n            match File::from_args(PathBuf::from(file_path), None, None) {\n                Err(e) => {\n                    exit_status = 2;\n                    writeln!(io::stderr(), \"{:?}: {}\", file_path, e)?;\n                }\n\n                Ok(f) => {\n                    if f.points_to_directory() && ! self.options.dir_action.treat_dirs_as_files() {\n                        match f.to_dir() {\n                            Ok(d)   => dirs.push(d),\n                            Err(e)  => writeln!(io::stderr(), \"{:?}: {}\", file_path, e)?,\n                        }\n                    }\n                    else {\n                        files.push(f);\n                    }\n                }\n            }\n        }\n\n        // We want to print a directory’s name before we list it, *except* in\n        // the case where it’s the only directory, *except* if there are any\n        // files to print as well. (It’s a double negative)\n\n        let no_files = files.is_empty();\n        let is_only_dir = dirs.len() == 1 && no_files;\n\n        self.options.filter.filter_argument_files(&mut files);\n        self.print_files(None, files)?;\n\n        self.print_dirs(dirs, no_files, is_only_dir, exit_status)\n    }\n\n    fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool, exit_status: i32) -> io::Result<i32> {\n        for dir in dir_files {\n\n            // Put a gap between directories, or between the list of files and\n            // the first directory.\n            if first {\n                first = false;\n            }\n            else {\n                writeln!(&mut self.writer)?;\n            }\n\n            if ! is_only_dir {\n                let mut bits = Vec::new();\n                escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());\n                writeln!(&mut self.writer, \"{}:\", ANSIStrings(&bits))?;\n            }\n\n            let mut children = Vec::new();\n            let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;\n            for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore) {\n                match file {\n                    Ok(file)        => children.push(file),\n                    Err((path, e))  => writeln!(io::stderr(), \"[{}: {}]\", path.display(), e)?,\n                }\n            };\n\n            self.options.filter.filter_child_files(&mut children);\n            self.options.filter.sort_files(&mut children);\n\n            if let Some(recurse_opts) = self.options.dir_action.recurse_options() {\n                let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;\n                if ! recurse_opts.tree && ! recurse_opts.is_too_deep(depth) {\n\n                    let mut child_dirs = Vec::new();\n                    for child_dir in children.iter().filter(|f| f.is_directory() && ! f.is_all_all) {\n                        match child_dir.to_dir() {\n                            Ok(d)   => child_dirs.push(d),\n                            Err(e)  => writeln!(io::stderr(), \"{}: {}\", child_dir.path.display(), e)?,\n                        }\n                    }\n\n                    self.print_files(Some(&dir), children)?;\n                    match self.print_dirs(child_dirs, false, false, exit_status) {\n                        Ok(_)   => (),\n                        Err(e)  => return Err(e),\n                    }\n                    continue;\n                }\n            }\n\n            self.print_files(Some(&dir), children)?;\n        }\n\n        Ok(exit_status)\n    }\n\n    /// Prints the list of files using whichever view is selected.\n    fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File<'_>>) -> io::Result<()> {\n        if files.is_empty() {\n            return Ok(());\n        }\n\n        let theme = &self.theme;\n        let View { ref mode, ref file_style, .. } = self.options.view;\n\n        match (mode, self.console_width) {\n            (Mode::Grid(ref opts), Some(console_width)) => {\n                let filter = &self.options.filter;\n                let r = grid::Render { files, theme, file_style, opts, console_width, filter };\n                r.render(&mut self.writer)\n            }\n\n            (Mode::Grid(_), None) |\n            (Mode::Lines,   _)    => {\n                let filter = &self.options.filter;\n                let r = lines::Render { files, theme, file_style, filter };\n                r.render(&mut self.writer)\n            }\n\n            (Mode::Details(ref opts), _) => {\n                let filter = &self.options.filter;\n                let recurse = self.options.dir_action.recurse_options();\n\n                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;\n                let git = self.git.as_ref();\n                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };\n                r.render(&mut self.writer)\n            }\n\n            (Mode::GridDetails(ref opts), Some(console_width)) => {\n                let grid = &opts.grid;\n                let details = &opts.details;\n                let row_threshold = opts.row_threshold;\n\n                let filter = &self.options.filter;\n                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;\n                let git = self.git.as_ref();\n\n                let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width };\n                r.render(&mut self.writer)\n            }\n\n            (Mode::GridDetails(ref opts), None) => {\n                let opts = &opts.to_details_options();\n                let filter = &self.options.filter;\n                let recurse = self.options.dir_action.recurse_options();\n                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;\n\n                let git = self.git.as_ref();\n                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };\n                r.render(&mut self.writer)\n            }\n        }\n    }\n}\n\n\nmod exits {\n\n    /// Exit code for when exa runs OK.\n    pub const SUCCESS: i32 = 0;\n\n    /// Exit code for when there was at least one I/O error during execution.\n    pub const RUNTIME_ERROR: i32 = 1;\n\n    /// Exit code for when the command-line options are invalid.\n    pub const OPTIONS_ERROR: i32 = 3;\n}\n"
  },
  {
    "path": "src/options/dir_action.rs",
    "content": "//! Parsing the options for `DirAction`.\n\nuse crate::options::parser::MatchedFlags;\nuse crate::options::{flags, OptionsError, NumberSource};\n\nuse crate::fs::dir_action::{DirAction, RecurseOptions};\n\n\nimpl DirAction {\n\n    /// Determine which action to perform when trying to list a directory.\n    /// There are three possible actions, and they overlap somewhat: the\n    /// `--tree` flag is another form of recursion, so those two are allowed\n    /// to both be present, but the `--list-dirs` flag is used separately.\n    pub fn deduce(matches: &MatchedFlags<'_>, can_tree: bool) -> Result<Self, OptionsError> {\n        let recurse = matches.has(&flags::RECURSE)?;\n        let as_file = matches.has(&flags::LIST_DIRS)?;\n        let tree    = matches.has(&flags::TREE)?;\n\n        if matches.is_strict() {\n            // Early check for --level when it wouldn’t do anything\n            if ! recurse && ! tree && matches.count(&flags::LEVEL) > 0 {\n                return Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));\n            }\n            else if recurse && as_file {\n                return Err(OptionsError::Conflict(&flags::RECURSE, &flags::LIST_DIRS));\n            }\n            else if tree && as_file {\n                return Err(OptionsError::Conflict(&flags::TREE, &flags::LIST_DIRS));\n            }\n        }\n\n        if tree && can_tree {\n            // Tree is only appropriate in details mode, so this has to\n            // examine the View, which should have already been deduced by now\n            Ok(Self::Recurse(RecurseOptions::deduce(matches, true)?))\n        }\n        else if recurse {\n            Ok(Self::Recurse(RecurseOptions::deduce(matches, false)?))\n        }\n        else if as_file {\n            Ok(Self::AsFile)\n        }\n        else {\n            Ok(Self::List)\n        }\n    }\n}\n\n\nimpl RecurseOptions {\n\n    /// Determine which files should be recursed into, based on the `--level`\n    /// flag’s value, and whether the `--tree` flag was passed, which was\n    /// determined earlier. The maximum level should be a number, and this\n    /// will fail with an `Err` if it isn’t.\n    pub fn deduce(matches: &MatchedFlags<'_>, tree: bool) -> Result<Self, OptionsError> {\n        if let Some(level) = matches.get(&flags::LEVEL)? {\n            let arg_str = level.to_string_lossy();\n            match arg_str.parse() {\n                Ok(l) => {\n                    Ok(Self { tree, max_depth: Some(l) })\n                }\n                Err(e) => {\n                    let source = NumberSource::Arg(&flags::LEVEL);\n                    Err(OptionsError::FailedParse(arg_str.to_string(), source, e))\n                }\n            }\n        }\n        else {\n            Ok(Self { tree, max_depth: None })\n        }\n    }\n}\n\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::options::flags;\n    use crate::options::parser::Flag;\n\n    macro_rules! test {\n        ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {\n            #[test]\n            fn $name() {\n                use crate::options::parser::Arg;\n                use crate::options::test::parse_for_test;\n                use crate::options::test::Strictnesses::*;\n\n                static TEST_ARGS: &[&Arg] = &[&flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, true)) {\n                    assert_eq!(result, $result);\n                }\n            }\n        };\n    }\n\n\n    // Default behaviour\n    test!(empty:           DirAction <- [];               Both => Ok(DirAction::List));\n\n    // Listing files as directories\n    test!(dirs_short:      DirAction <- [\"-d\"];           Both => Ok(DirAction::AsFile));\n    test!(dirs_long:       DirAction <- [\"--list-dirs\"];  Both => Ok(DirAction::AsFile));\n\n    // Recursing\n    use self::DirAction::Recurse;\n    test!(rec_short:       DirAction <- [\"-R\"];                           Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));\n    test!(rec_long:        DirAction <- [\"--recurse\"];                    Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));\n    test!(rec_lim_short:   DirAction <- [\"-RL4\"];                         Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));\n    test!(rec_lim_short_2: DirAction <- [\"-RL=5\"];                        Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));\n    test!(rec_lim_long:    DirAction <- [\"--recurse\", \"--level\", \"666\"];  Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));\n    test!(rec_lim_long_2:  DirAction <- [\"--recurse\", \"--level=0118\"];    Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));\n    test!(tree:            DirAction <- [\"--tree\"];                       Both => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));\n    test!(rec_tree:        DirAction <- [\"--recurse\", \"--tree\"];          Both => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));\n    test!(rec_short_tree:  DirAction <- [\"-TR\"];                          Both => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));\n\n    // Overriding --list-dirs, --recurse, and --tree\n    test!(dirs_recurse:    DirAction <- [\"--list-dirs\", \"--recurse\"];     Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));\n    test!(dirs_tree:       DirAction <- [\"--list-dirs\", \"--tree\"];        Last => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));\n    test!(just_level:      DirAction <- [\"--level=4\"];                    Last => Ok(DirAction::List));\n\n    test!(dirs_recurse_2:  DirAction <- [\"--list-dirs\", \"--recurse\"]; Complain => Err(OptionsError::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));\n    test!(dirs_tree_2:     DirAction <- [\"--list-dirs\", \"--tree\"];    Complain => Err(OptionsError::Conflict(&flags::TREE,    &flags::LIST_DIRS)));\n    test!(just_level_2:    DirAction <- [\"--level=4\"];                Complain => Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)));\n\n\n    // Overriding levels\n    test!(overriding_1:    DirAction <- [\"-RL=6\", \"-L=7\"];                Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(7) })));\n    test!(overriding_2:    DirAction <- [\"-RL=6\", \"-L=7\"];            Complain => Err(OptionsError::Duplicate(Flag::Short(b'L'), Flag::Short(b'L'))));\n}\n"
  },
  {
    "path": "src/options/error.rs",
    "content": "use std::ffi::OsString;\nuse std::fmt;\nuse std::num::ParseIntError;\n\nuse crate::options::flags;\nuse crate::options::parser::{Arg, Flag, ParseError};\n\n\n/// Something wrong with the combination of options the user has picked.\n#[derive(PartialEq, Eq, Debug)]\npub enum OptionsError {\n\n    /// There was an error (from `getopts`) parsing the arguments.\n    Parse(ParseError),\n\n    /// The user supplied an illegal choice to an Argument.\n    BadArgument(&'static Arg, OsString),\n\n    /// The user supplied a set of options that are unsupported\n    Unsupported(String),\n\n    /// An option was given twice or more in strict mode.\n    Duplicate(Flag, Flag),\n\n    /// Two options were given that conflict with one another.\n    Conflict(&'static Arg, &'static Arg),\n\n    /// An option was given that does nothing when another one either is or\n    /// isn’t present.\n    Useless(&'static Arg, bool, &'static Arg),\n\n    /// An option was given that does nothing when either of two other options\n    /// are not present.\n    Useless2(&'static Arg, &'static Arg, &'static Arg),\n\n    /// A very specific edge case where --tree can’t be used with --all twice.\n    TreeAllAll,\n\n    /// A numeric option was given that failed to be parsed as a number.\n    FailedParse(String, NumberSource, ParseIntError),\n\n    /// A glob ignore was given that failed to be parsed as a pattern.\n    FailedGlobPattern(String),\n}\n\n/// The source of a string that failed to be parsed as a number.\n#[derive(PartialEq, Eq, Debug)]\npub enum NumberSource {\n\n    /// It came... from a command-line argument!\n    Arg(&'static Arg),\n\n    /// It came... from the enviroment!\n    Env(&'static str),\n}\n\nimpl From<glob::PatternError> for OptionsError {\n    fn from(error: glob::PatternError) -> Self {\n        Self::FailedGlobPattern(error.to_string())\n    }\n}\n\nimpl fmt::Display for NumberSource {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Arg(arg) => write!(f, \"option {}\", arg),\n            Self::Env(env) => write!(f, \"environment variable {}\", env),\n        }\n    }\n}\n\nimpl fmt::Display for OptionsError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        use crate::options::parser::TakesValue;\n\n        match self {\n            Self::BadArgument(arg, attempt) => {\n                if let TakesValue::Necessary(Some(values)) = arg.takes_value {\n                    write!(f, \"Option {} has no {:?} setting ({})\", arg, attempt, Choices(values))\n                }\n                else {\n                    write!(f, \"Option {} has no {:?} setting\", arg, attempt)\n                }\n            }\n            Self::Parse(e)                   => write!(f, \"{}\", e),\n            Self::Unsupported(e)             => write!(f, \"{}\", e),\n            Self::Conflict(a, b)             => write!(f, \"Option {} conflicts with option {}\", a, b),\n            Self::Duplicate(a, b) if a == b  => write!(f, \"Flag {} was given twice\", a),\n            Self::Duplicate(a, b)            => write!(f, \"Flag {} conflicts with flag {}\", a, b),\n            Self::Useless(a, false, b)       => write!(f, \"Option {} is useless without option {}\", a, b),\n            Self::Useless(a, true, b)        => write!(f, \"Option {} is useless given option {}\", a, b),\n            Self::Useless2(a, b1, b2)        => write!(f, \"Option {} is useless without options {} or {}\", a, b1, b2),\n            Self::TreeAllAll                 => write!(f, \"Option --tree is useless given --all --all\"),\n            Self::FailedParse(s, n, e)       => write!(f, \"Value {:?} not valid for {}: {}\", s, n, e),\n            Self::FailedGlobPattern(ref e)   => write!(f, \"Failed to parse glob pattern: {}\", e),\n        }\n    }\n}\n\nimpl OptionsError {\n\n    /// Try to second-guess what the user was trying to do, depending on what\n    /// went wrong.\n    pub fn suggestion(&self) -> Option<&'static str> {\n        // ‘ls -lt’ and ‘ls -ltr’ are common combinations\n        match self {\n            Self::BadArgument(time, r) if *time == &flags::TIME && r == \"r\" => {\n                Some(\"To sort oldest files last, try \\\"--sort oldest\\\", or just \\\"-sold\\\"\")\n            }\n            Self::Parse(ParseError::NeedsValue { ref flag, .. }) if *flag == Flag::Short(b't') => {\n                Some(\"To sort newest files last, try \\\"--sort newest\\\", or just \\\"-snew\\\"\")\n            }\n            _ => {\n                None\n            }\n        }\n    }\n}\n\n\n/// A list of legal choices for an argument-taking option.\n#[derive(PartialEq, Eq, Debug)]\npub struct Choices(pub &'static [&'static str]);\n\nimpl fmt::Display for Choices {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"choices: {}\", self.0.join(\", \"))\n    }\n}\n"
  },
  {
    "path": "src/options/file_name.rs",
    "content": "use crate::options::{flags, OptionsError, NumberSource};\nuse crate::options::parser::MatchedFlags;\nuse crate::options::vars::{self, Vars};\n\nuse crate::output::file_name::{Options, Classify, ShowIcons};\n\n\nimpl Options {\n    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        let classify = Classify::deduce(matches)?;\n        let show_icons = ShowIcons::deduce(matches, vars)?;\n\n        Ok(Self { classify, show_icons })\n    }\n}\n\nimpl Classify {\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let flagged = matches.has(&flags::CLASSIFY)?;\n\n        if flagged { Ok(Self::AddFileIndicators) }\n              else { Ok(Self::JustFilenames) }\n    }\n}\n\nimpl ShowIcons {\n    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        if matches.has(&flags::NO_ICONS)? || !matches.has(&flags::ICONS)? {\n            Ok(Self::Off)\n        }\n        else if let Some(columns) = vars.get(vars::EXA_ICON_SPACING).and_then(|s| s.into_string().ok()) {\n            match columns.parse() {\n                Ok(width) => {\n                    Ok(Self::On(width))\n                }\n                Err(e) => {\n                    let source = NumberSource::Env(vars::EXA_ICON_SPACING);\n                    Err(OptionsError::FailedParse(columns, source, e))\n                }\n            }\n        }\n        else {\n            Ok(Self::On(1))\n        }\n    }\n}\n"
  },
  {
    "path": "src/options/filter.rs",
    "content": "//! Parsing the options for `FileFilter`.\n\nuse crate::fs::DotFilter;\nuse crate::fs::filter::{FileFilter, SortField, SortCase, IgnorePatterns, GitIgnore};\n\nuse crate::options::{flags, OptionsError};\nuse crate::options::parser::MatchedFlags;\n\n\nimpl FileFilter {\n\n    /// Determines which of all the file filter options to use.\n    pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        Ok(Self {\n            list_dirs_first:  matches.has(&flags::DIRS_FIRST)?,\n            reverse:          matches.has(&flags::REVERSE)?,\n            only_dirs:        matches.has(&flags::ONLY_DIRS)?,\n            sort_field:       SortField::deduce(matches)?,\n            dot_filter:       DotFilter::deduce(matches)?,\n            ignore_patterns:  IgnorePatterns::deduce(matches)?,\n            git_ignore:       GitIgnore::deduce(matches)?,\n        })\n    }\n}\n\nimpl SortField {\n\n    /// Determines which sort field to use based on the `--sort` argument.\n    /// This argument’s value can be one of several flags, listed above.\n    /// Returns the default sort field if none is given, or `Err` if the\n    /// value doesn’t correspond to a sort field we know about.\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let word = match matches.get(&flags::SORT)? {\n            Some(w)  => w,\n            None     => return Ok(Self::default()),\n        };\n\n        // Get String because we can’t match an OsStr\n        let word = match word.to_str() {\n            Some(w)  => w,\n            None     => return Err(OptionsError::BadArgument(&flags::SORT, word.into()))\n        };\n\n        let field = match word {\n            \"name\" | \"filename\" => {\n                Self::Name(SortCase::AaBbCc)\n            }\n            \"Name\" | \"Filename\" => {\n                Self::Name(SortCase::ABCabc)\n            }\n            \".name\" | \".filename\" => {\n                Self::NameMixHidden(SortCase::AaBbCc)\n            }\n            \".Name\" | \".Filename\" => {\n                Self::NameMixHidden(SortCase::ABCabc)\n            }\n            \"size\" | \"filesize\" => {\n                Self::Size\n            }\n            \"ext\" | \"extension\" => {\n                Self::Extension(SortCase::AaBbCc)\n            }\n            \"Ext\" | \"Extension\" => {\n                Self::Extension(SortCase::ABCabc)\n            }\n\n            // “new” sorts oldest at the top and newest at the bottom; “old”\n            // sorts newest at the top and oldest at the bottom. I think this\n            // is the right way round to do this: “size” puts the smallest at\n            // the top and the largest at the bottom, doesn’t it?\n            \"date\" | \"time\" | \"mod\" | \"modified\" | \"new\" | \"newest\" => {\n                Self::ModifiedDate\n            }\n\n            // Similarly, “age” means that files with the least age (the\n            // newest files) get sorted at the top, and files with the most\n            // age (the oldest) at the bottom.\n            \"age\" | \"old\" | \"oldest\" => {\n                Self::ModifiedAge\n            }\n\n            \"ch\" | \"changed\" => {\n                Self::ChangedDate\n            }\n            \"acc\" | \"accessed\" => {\n                Self::AccessedDate\n            }\n            \"cr\" | \"created\" => {\n                Self::CreatedDate\n            }\n            #[cfg(unix)]\n            \"inode\" => {\n                Self::FileInode\n            }\n            \"type\" => {\n                Self::FileType\n            }\n            \"none\" => {\n                Self::Unsorted\n            }\n            _ => {\n                return Err(OptionsError::BadArgument(&flags::SORT, word.into()));\n            }\n        };\n\n        Ok(field)\n    }\n}\n\n\n// I’ve gone back and forth between whether to sort case-sensitively or\n// insensitively by default. The default string sort in most programming\n// languages takes each character’s ASCII value into account, sorting\n// “Documents” before “apps”, but there’s usually an option to ignore\n// characters’ case, putting “apps” before “Documents”.\n//\n// The argument for following case is that it’s easy to forget whether an item\n// begins with an uppercase or lowercase letter and end up having to scan both\n// the uppercase and lowercase sub-lists to find the item you want. If you\n// happen to pick the sublist it’s not in, it looks like it’s missing, which\n// is worse than if you just take longer to find it.\n// (https://ux.stackexchange.com/a/79266)\n//\n// The argument for ignoring case is that it makes exa sort files differently\n// from shells. A user would expect a directory’s files to be in the same\n// order if they used “exa ~/directory” or “exa ~/directory/*”, but exa sorts\n// them in the first case, and the shell in the second case, so they wouldn’t\n// be exactly the same if exa does something non-conventional.\n//\n// However, exa already sorts files differently: it uses natural sorting from\n// the natord crate, sorting the string “2” before “10” because the number’s\n// smaller, because that’s usually what the user expects to happen. Users will\n// name their files with numbers expecting them to be treated like numbers,\n// rather than lists of numeric characters.\n//\n// In the same way, users will name their files with letters expecting the\n// order of the letters to matter, rather than each letter’s character’s ASCII\n// value. So exa breaks from tradition and ignores case while sorting:\n// “apps” first, then “Documents”.\n//\n// You can get the old behaviour back by sorting with `--sort=Name`.\nimpl Default for SortField {\n    fn default() -> Self {\n        Self::Name(SortCase::AaBbCc)\n    }\n}\n\n\nimpl DotFilter {\n\n    /// Determines the dot filter based on how many `--all` options were\n    /// given: one will show dotfiles, but two will show `.` and `..` too.\n    ///\n    /// It also checks for the `--tree` option in strict mode, because of a\n    /// special case where `--tree --all --all` won’t work: listing the\n    /// parent directory in tree mode would loop onto itself!\n    pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let count = matches.count(&flags::ALL);\n\n        if count == 0 {\n            Ok(Self::JustFiles)\n        }\n        else if count == 1 {\n            Ok(Self::Dotfiles)\n        }\n        else if matches.count(&flags::TREE) > 0 {\n            Err(OptionsError::TreeAllAll)\n        }\n        else if count >= 3 && matches.is_strict() {\n            Err(OptionsError::Conflict(&flags::ALL, &flags::ALL))\n        }\n        else {\n            Ok(Self::DotfilesAndDots)\n        }\n    }\n}\n\n\nimpl IgnorePatterns {\n\n    /// Determines the set of glob patterns to use based on the\n    /// `--ignore-glob` argument’s value. This is a list of strings\n    /// separated by pipe (`|`) characters, given in any order.\n    pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n\n        // If there are no inputs, we return a set of patterns that doesn’t\n        // match anything, rather than, say, `None`.\n        let inputs = match matches.get(&flags::IGNORE_GLOB)? {\n            Some(is)  => is,\n            None      => return Ok(Self::empty()),\n        };\n\n        // Awkwardly, though, a glob pattern can be invalid, and we need to\n        // deal with invalid patterns somehow.\n        let (patterns, mut errors) = Self::parse_from_iter(inputs.to_string_lossy().split('|'));\n\n        // It can actually return more than one glob error,\n        // but we only use one. (TODO)\n        match errors.pop() {\n            Some(e)  => Err(e.into()),\n            None     => Ok(patterns),\n        }\n    }\n}\n\n\nimpl GitIgnore {\n    pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        if matches.has(&flags::GIT_IGNORE)? {\n            Ok(Self::CheckAndIgnore)\n        }\n        else {\n            Ok(Self::Off)\n        }\n    }\n}\n\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use std::ffi::OsString;\n    use crate::options::flags;\n    use crate::options::parser::Flag;\n\n    macro_rules! test {\n        ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {\n            #[test]\n            fn $name() {\n                use crate::options::parser::Arg;\n                use crate::options::test::parse_for_test;\n                use crate::options::test::Strictnesses::*;\n\n                static TEST_ARGS: &[&Arg] = &[ &flags::SORT, &flags::ALL, &flags::TREE, &flags::IGNORE_GLOB, &flags::GIT_IGNORE ];\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {\n                    assert_eq!(result, $result);\n                }\n            }\n        };\n    }\n\n    mod sort_fields {\n        use super::*;\n\n        // Default behaviour\n        test!(empty:         SortField <- [];                  Both => Ok(SortField::default()));\n\n        // Sort field arguments\n        test!(one_arg:       SortField <- [\"--sort=mod\"];       Both => Ok(SortField::ModifiedDate));\n        test!(one_long:      SortField <- [\"--sort=size\"];     Both => Ok(SortField::Size));\n        test!(one_short:     SortField <- [\"-saccessed\"];      Both => Ok(SortField::AccessedDate));\n        test!(lowercase:     SortField <- [\"--sort\", \"name\"];  Both => Ok(SortField::Name(SortCase::AaBbCc)));\n        test!(uppercase:     SortField <- [\"--sort\", \"Name\"];  Both => Ok(SortField::Name(SortCase::ABCabc)));\n        test!(old:           SortField <- [\"--sort\", \"new\"];   Both => Ok(SortField::ModifiedDate));\n        test!(oldest:        SortField <- [\"--sort=newest\"];   Both => Ok(SortField::ModifiedDate));\n        test!(new:           SortField <- [\"--sort\", \"old\"];   Both => Ok(SortField::ModifiedAge));\n        test!(newest:        SortField <- [\"--sort=oldest\"];   Both => Ok(SortField::ModifiedAge));\n        test!(age:           SortField <- [\"-sage\"];           Both => Ok(SortField::ModifiedAge));\n\n        test!(mix_hidden_lowercase:     SortField <- [\"--sort\", \".name\"];  Both => Ok(SortField::NameMixHidden(SortCase::AaBbCc)));\n        test!(mix_hidden_uppercase:     SortField <- [\"--sort\", \".Name\"];  Both => Ok(SortField::NameMixHidden(SortCase::ABCabc)));\n\n        // Errors\n        test!(error:         SortField <- [\"--sort=colour\"];   Both => Err(OptionsError::BadArgument(&flags::SORT, OsString::from(\"colour\"))));\n\n        // Overriding\n        test!(overridden:    SortField <- [\"--sort=cr\",       \"--sort\", \"mod\"];     Last => Ok(SortField::ModifiedDate));\n        test!(overridden_2:  SortField <- [\"--sort\", \"none\",  \"--sort=Extension\"];  Last => Ok(SortField::Extension(SortCase::ABCabc)));\n        test!(overridden_3:  SortField <- [\"--sort=cr\",       \"--sort\", \"mod\"];     Complain => Err(OptionsError::Duplicate(Flag::Long(\"sort\"), Flag::Long(\"sort\"))));\n        test!(overridden_4:  SortField <- [\"--sort\", \"none\",  \"--sort=Extension\"];  Complain => Err(OptionsError::Duplicate(Flag::Long(\"sort\"), Flag::Long(\"sort\"))));\n    }\n\n\n    mod dot_filters {\n        use super::*;\n\n        // Default behaviour\n        test!(empty:      DotFilter <- [];               Both => Ok(DotFilter::JustFiles));\n\n        // --all\n        test!(all:        DotFilter <- [\"--all\"];        Both => Ok(DotFilter::Dotfiles));\n        test!(all_all:    DotFilter <- [\"--all\", \"-a\"];  Both => Ok(DotFilter::DotfilesAndDots));\n        test!(all_all_2:  DotFilter <- [\"-aa\"];          Both => Ok(DotFilter::DotfilesAndDots));\n\n        test!(all_all_3:  DotFilter <- [\"-aaa\"];         Last => Ok(DotFilter::DotfilesAndDots));\n        test!(all_all_4:  DotFilter <- [\"-aaa\"];         Complain => Err(OptionsError::Conflict(&flags::ALL, &flags::ALL)));\n\n        // --all and --tree\n        test!(tree_a:     DotFilter <- [\"-Ta\"];          Both => Ok(DotFilter::Dotfiles));\n        test!(tree_aa:    DotFilter <- [\"-Taa\"];         Both => Err(OptionsError::TreeAllAll));\n        test!(tree_aaa:   DotFilter <- [\"-Taaa\"];        Both => Err(OptionsError::TreeAllAll));\n    }\n\n\n    mod ignore_patterns {\n        use super::*;\n        use std::iter::FromIterator;\n\n        fn pat(string: &'static str) -> glob::Pattern {\n            glob::Pattern::new(string).unwrap()\n        }\n\n        // Various numbers of globs\n        test!(none:   IgnorePatterns <- [];                                        Both => Ok(IgnorePatterns::empty()));\n        test!(one:    IgnorePatterns <- [\"--ignore-glob\", \"*.ogg\"];                Both => Ok(IgnorePatterns::from_iter(vec![ pat(\"*.ogg\") ])));\n        test!(two:    IgnorePatterns <- [\"--ignore-glob=*.ogg|*.MP3\"];             Both => Ok(IgnorePatterns::from_iter(vec![ pat(\"*.ogg\"), pat(\"*.MP3\") ])));\n        test!(loads:  IgnorePatterns <- [\"-I*|?|.|*\"];                             Both => Ok(IgnorePatterns::from_iter(vec![ pat(\"*\"), pat(\"?\"), pat(\".\"), pat(\"*\") ])));\n\n        // Overriding\n        test!(overridden:   IgnorePatterns <- [\"-I=*.ogg\",    \"-I\", \"*.mp3\"];      Last => Ok(IgnorePatterns::from_iter(vec![ pat(\"*.mp3\") ])));\n        test!(overridden_2: IgnorePatterns <- [\"-I\", \"*.OGG\", \"-I*.MP3\"];          Last => Ok(IgnorePatterns::from_iter(vec![ pat(\"*.MP3\") ])));\n        test!(overridden_3: IgnorePatterns <- [\"-I=*.ogg\",    \"-I\", \"*.mp3\"];  Complain => Err(OptionsError::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));\n        test!(overridden_4: IgnorePatterns <- [\"-I\", \"*.OGG\", \"-I*.MP3\"];      Complain => Err(OptionsError::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));\n    }\n\n\n    mod git_ignores {\n        use super::*;\n\n        test!(off:  GitIgnore <- [];                Both => Ok(GitIgnore::Off));\n        test!(on:   GitIgnore <- [\"--git-ignore\"];  Both => Ok(GitIgnore::CheckAndIgnore));\n    }\n}\n"
  },
  {
    "path": "src/options/flags.rs",
    "content": "use crate::options::parser::{Arg, Args, TakesValue, Values};\n\n\n// exa options\npub static VERSION: Arg = Arg { short: Some(b'v'), long: \"version\",  takes_value: TakesValue::Forbidden };\npub static HELP:    Arg = Arg { short: Some(b'?'), long: \"help\",     takes_value: TakesValue::Forbidden };\n\n// display options\npub static ONE_LINE: Arg = Arg { short: Some(b'1'), long: \"oneline\",  takes_value: TakesValue::Forbidden };\npub static LONG:     Arg = Arg { short: Some(b'l'), long: \"long\",     takes_value: TakesValue::Forbidden };\npub static GRID:     Arg = Arg { short: Some(b'G'), long: \"grid\",     takes_value: TakesValue::Forbidden };\npub static ACROSS:   Arg = Arg { short: Some(b'x'), long: \"across\",   takes_value: TakesValue::Forbidden };\npub static RECURSE:  Arg = Arg { short: Some(b'R'), long: \"recurse\",  takes_value: TakesValue::Forbidden };\npub static TREE:     Arg = Arg { short: Some(b'T'), long: \"tree\",     takes_value: TakesValue::Forbidden };\npub static CLASSIFY: Arg = Arg { short: Some(b'F'), long: \"classify\", takes_value: TakesValue::Forbidden };\n\npub static COLOR:  Arg = Arg { short: None, long: \"color\",  takes_value: TakesValue::Necessary(Some(COLOURS)) };\npub static COLOUR: Arg = Arg { short: None, long: \"colour\", takes_value: TakesValue::Necessary(Some(COLOURS)) };\nconst COLOURS: &[&str] = &[\"always\", \"auto\", \"never\"];\n\npub static COLOR_SCALE:  Arg = Arg { short: None, long: \"color-scale\",  takes_value: TakesValue::Forbidden };\npub static COLOUR_SCALE: Arg = Arg { short: None, long: \"colour-scale\", takes_value: TakesValue::Forbidden };\n\n// filtering and sorting options\npub static ALL:         Arg = Arg { short: Some(b'a'), long: \"all\",         takes_value: TakesValue::Forbidden };\npub static LIST_DIRS:   Arg = Arg { short: Some(b'd'), long: \"list-dirs\",   takes_value: TakesValue::Forbidden };\npub static LEVEL:       Arg = Arg { short: Some(b'L'), long: \"level\",       takes_value: TakesValue::Necessary(None) };\npub static REVERSE:     Arg = Arg { short: Some(b'r'), long: \"reverse\",     takes_value: TakesValue::Forbidden };\npub static SORT:        Arg = Arg { short: Some(b's'), long: \"sort\",        takes_value: TakesValue::Necessary(Some(SORTS)) };\npub static IGNORE_GLOB: Arg = Arg { short: Some(b'I'), long: \"ignore-glob\", takes_value: TakesValue::Necessary(None) };\npub static GIT_IGNORE:  Arg = Arg { short: None, long: \"git-ignore\",           takes_value: TakesValue::Forbidden };\npub static DIRS_FIRST:  Arg = Arg { short: None, long: \"group-directories-first\",  takes_value: TakesValue::Forbidden };\npub static ONLY_DIRS:   Arg = Arg { short: Some(b'D'), long: \"only-dirs\", takes_value: TakesValue::Forbidden };\nconst SORTS: Values = &[ \"name\", \"Name\", \"size\", \"extension\",\n                         \"Extension\", \"modified\", \"changed\", \"accessed\",\n                         \"created\", \"inode\", \"type\", \"none\" ];\n\n// display options\npub static BINARY:     Arg = Arg { short: Some(b'b'), long: \"binary\",     takes_value: TakesValue::Forbidden };\npub static BYTES:      Arg = Arg { short: Some(b'B'), long: \"bytes\",      takes_value: TakesValue::Forbidden };\npub static GROUP:      Arg = Arg { short: Some(b'g'), long: \"group\",      takes_value: TakesValue::Forbidden };\npub static NUMERIC:    Arg = Arg { short: Some(b'n'), long: \"numeric\",    takes_value: TakesValue::Forbidden };\npub static HEADER:     Arg = Arg { short: Some(b'h'), long: \"header\",     takes_value: TakesValue::Forbidden };\npub static ICONS:      Arg = Arg { short: None,       long: \"icons\",      takes_value: TakesValue::Forbidden };\npub static INODE:      Arg = Arg { short: Some(b'i'), long: \"inode\",      takes_value: TakesValue::Forbidden };\npub static LINKS:      Arg = Arg { short: Some(b'H'), long: \"links\",      takes_value: TakesValue::Forbidden };\npub static MODIFIED:   Arg = Arg { short: Some(b'm'), long: \"modified\",   takes_value: TakesValue::Forbidden };\npub static CHANGED:    Arg = Arg { short: None,       long: \"changed\",    takes_value: TakesValue::Forbidden };\npub static BLOCKS:     Arg = Arg { short: Some(b'S'), long: \"blocks\",     takes_value: TakesValue::Forbidden };\npub static TIME:       Arg = Arg { short: Some(b't'), long: \"time\",       takes_value: TakesValue::Necessary(Some(TIMES)) };\npub static ACCESSED:   Arg = Arg { short: Some(b'u'), long: \"accessed\",   takes_value: TakesValue::Forbidden };\npub static CREATED:    Arg = Arg { short: Some(b'U'), long: \"created\",    takes_value: TakesValue::Forbidden };\npub static TIME_STYLE: Arg = Arg { short: None,       long: \"time-style\", takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };\nconst TIMES: Values = &[\"modified\", \"changed\", \"accessed\", \"created\"];\nconst TIME_STYLES: Values = &[\"default\", \"long-iso\", \"full-iso\", \"iso\"];\n\n// suppressing columns\npub static NO_PERMISSIONS: Arg = Arg { short: None, long: \"no-permissions\", takes_value: TakesValue::Forbidden };\npub static NO_FILESIZE: Arg = Arg { short: None, long: \"no-filesize\", takes_value: TakesValue::Forbidden };\npub static NO_USER: Arg = Arg { short: None, long: \"no-user\", takes_value: TakesValue::Forbidden };\npub static NO_TIME: Arg = Arg { short: None, long: \"no-time\", takes_value: TakesValue::Forbidden };\npub static NO_ICONS: Arg = Arg { short: None, long: \"no-icons\", takes_value: TakesValue::Forbidden };\n\n// optional feature options\npub static GIT:       Arg = Arg { short: None,       long: \"git\",               takes_value: TakesValue::Forbidden };\npub static EXTENDED:  Arg = Arg { short: Some(b'@'), long: \"extended\",          takes_value: TakesValue::Forbidden };\npub static OCTAL:     Arg = Arg { short: None,       long: \"octal-permissions\", takes_value: TakesValue::Forbidden };\n\n\npub static ALL_ARGS: Args = Args(&[\n    &VERSION, &HELP,\n\n    &ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY,\n    &COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE,\n\n    &ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,\n    &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS,\n\n    &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,\n    &BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,\n    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,\n\n    &GIT, &EXTENDED, &OCTAL\n]);\n"
  },
  {
    "path": "src/options/help.rs",
    "content": "use std::fmt;\n\nuse crate::fs::feature::xattr;\nuse crate::options::flags;\nuse crate::options::parser::MatchedFlags;\n\n\nstatic USAGE_PART1: &str = \"Usage:\n  exa [options] [files...]\n\nMETA OPTIONS\n  -?, --help         show list of command-line options\n  -v, --version      show version of exa\n\nDISPLAY OPTIONS\n  -1, --oneline      display one entry per line\n  -l, --long         display extended file metadata as a table\n  -G, --grid         display entries as a grid (default)\n  -x, --across       sort the grid across, rather than downwards\n  -R, --recurse      recurse into directories\n  -T, --tree         recurse into directories as a tree\n  -F, --classify     display type indicator by file names\n  --colo[u]r=WHEN    when to use terminal colours (always, auto, never)\n  --colo[u]r-scale   highlight levels of file sizes distinctly\n  --icons            display icons\n  --no-icons         don't display icons (always overrides --icons)\n\nFILTERING AND SORTING OPTIONS\n  -a, --all                  show hidden and 'dot' files\n  -d, --list-dirs            list directories as files; don't list their contents\n  -L, --level DEPTH          limit the depth of recursion\n  -r, --reverse              reverse the sort order\n  -s, --sort SORT_FIELD      which field to sort by\n  --group-directories-first  list directories before other files\n  -D, --only-dirs            list only directories\n  -I, --ignore-glob GLOBS    glob patterns (pipe-separated) of files to ignore\";\n\n  static USAGE_PART2: &str = \"  \\\n  Valid sort fields:         name, Name, extension, Extension, size, type,\n                             modified, accessed, created, inode, and none.\n                             date, time, old, and new all refer to modified.\n\nLONG VIEW OPTIONS\n  -b, --binary         list file sizes with binary prefixes\n  -B, --bytes          list file sizes in bytes, without any prefixes\n  -g, --group          list each file's group\n  -h, --header         add a header row to each column\n  -H, --links          list each file's number of hard links\n  -i, --inode          list each file's inode number\n  -m, --modified       use the modified timestamp field\n  -n, --numeric        list numeric user and group IDs\n  -S, --blocks         show number of file system blocks\n  -t, --time FIELD     which timestamp field to list (modified, accessed, created)\n  -u, --accessed       use the accessed timestamp field\n  -U, --created        use the created timestamp field\n  --changed            use the changed timestamp field\n  --time-style         how to format timestamps (default, iso, long-iso, full-iso)\n  --no-permissions     suppress the permissions field\n  --octal-permissions  list each file's permission in octal format\n  --no-filesize        suppress the filesize field\n  --no-user            suppress the user field\n  --no-time            suppress the time field\";\n\nstatic GIT_FILTER_HELP: &str = \"  --git-ignore               ignore files mentioned in '.gitignore'\";\nstatic GIT_VIEW_HELP:   &str = \"  --git                list each file's Git status, if tracked or ignored\";\nstatic EXTENDED_HELP:   &str = \"  -@, --extended       list each file's extended attributes and sizes\";\n\n\n/// All the information needed to display the help text, which depends\n/// on which features are enabled and whether the user only wants to\n/// see one section’s help.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub struct HelpString;\n\nimpl HelpString {\n\n    /// Determines how to show help, if at all, based on the user’s\n    /// command-line arguments. This one works backwards from the other\n    /// ‘deduce’ functions, returning Err if help needs to be shown.\n    ///\n    /// We don’t do any strict-mode error checking here: it’s OK to give\n    /// the --help or --long flags more than once. Actually checking for\n    /// errors when the user wants help is kind of petty!\n    pub fn deduce(matches: &MatchedFlags<'_>) -> Option<Self> {\n        if matches.count(&flags::HELP) > 0 {\n            Some(Self)\n        }\n        else {\n            None\n        }\n    }\n}\n\nimpl fmt::Display for HelpString {\n\n    /// Format this help options into an actual string of help\n    /// text to be displayed to the user.\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        write!(f, \"{}\", USAGE_PART1)?;\n\n        if cfg!(feature = \"git\") {\n            write!(f, \"\\n{}\", GIT_FILTER_HELP)?;\n        }\n\n        write!(f, \"\\n{}\", USAGE_PART2)?;\n\n        if cfg!(feature = \"git\") {\n            write!(f, \"\\n{}\", GIT_VIEW_HELP)?;\n        }\n\n        if xattr::ENABLED {\n            write!(f, \"\\n{}\", EXTENDED_HELP)?;\n        }\n\n        writeln!(f)\n    }\n}\n\n\n#[cfg(test)]\nmod test {\n    use crate::options::{Options, OptionsResult};\n    use std::ffi::OsStr;\n\n    #[test]\n    fn help() {\n        let args = vec![ OsStr::new(\"--help\") ];\n        let opts = Options::parse(args, &None);\n        assert!(matches!(opts, OptionsResult::Help(_)));\n    }\n\n    #[test]\n    fn help_with_file() {\n        let args = vec![ OsStr::new(\"--help\"), OsStr::new(\"me\") ];\n        let opts = Options::parse(args, &None);\n        assert!(matches!(opts, OptionsResult::Help(_)));\n    }\n\n    #[test]\n    fn unhelpful() {\n        let args = vec![];\n        let opts = Options::parse(args, &None);\n        assert!(! matches!(opts, OptionsResult::Help(_)))  // no help when --help isn’t passed\n    }\n}\n"
  },
  {
    "path": "src/options/mod.rs",
    "content": "//! Parsing command-line strings into exa options.\n//!\n//! This module imports exa’s configuration types, such as `View` (the details\n//! of displaying multiple files) and `DirAction` (what to do when encountering\n//! a directory), and implements `deduce` methods on them so they can be\n//! configured using command-line options.\n//!\n//!\n//! ## Useless and overridden options\n//!\n//! Let’s say exa was invoked with just one argument: `exa --inode`. The\n//! `--inode` option is used in the details view, where it adds the inode\n//! column to the output. But because the details view is *only* activated with\n//! the `--long` argument, adding `--inode` without it would not have any\n//! effect.\n//!\n//! For a long time, exa’s philosophy was that the user should be warned\n//! whenever they could be mistaken like this. If you tell exa to display the\n//! inode, and it *doesn’t* display the inode, isn’t that more annoying than\n//! having it throw an error back at you?\n//!\n//! However, this doesn’t take into account *configuration*. Say a user wants\n//! to configure exa so that it lists inodes in the details view, but otherwise\n//! functions normally. A common way to do this for command-line programs is to\n//! define a shell alias that specifies the details they want to use every\n//! time. For the inode column, the alias would be:\n//!\n//! `alias exa=\"exa --inode\"`\n//!\n//! Using this alias means that although the inode column will be shown in the\n//! details view, you’re now *only* allowed to use the details view, as any\n//! other view type will result in an error. Oops!\n//!\n//! Another example is when an option is specified twice, such as `exa\n//! --sort=Name --sort=size`. Did the user change their mind about sorting, and\n//! accidentally specify the option twice?\n//!\n//! Again, exa rejected this case, throwing an error back to the user instead\n//! of trying to guess how they want their output sorted. And again, this\n//! doesn’t take into account aliases being used to set defaults. A user who\n//! wants their files to be sorted case-insensitively may configure their shell\n//! with the following:\n//!\n//! `alias exa=\"exa --sort=Name\"`\n//!\n//! Just like the earlier example, the user now can’t use any other sort order,\n//! because exa refuses to guess which one they meant. It’s *more* annoying to\n//! have to go back and edit the command than if there were no error.\n//!\n//! Fortunately, there’s a heuristic for telling which options came from an\n//! alias and which came from the actual command-line: aliased options are\n//! nearer the beginning of the options array, and command-line options are\n//! nearer the end. This means that after the options have been parsed, exa\n//! needs to traverse them *backwards* to find the last-most-specified one.\n//!\n//! For example, invoking exa with `exa --sort=size` when that alias is present\n//! would result in a full command-line of:\n//!\n//! `exa --sort=Name --sort=size`\n//!\n//! `--sort=size` should override `--sort=Name` because it’s closer to the end\n//! of the arguments array. In fact, because there’s no way to tell where the\n//! arguments came from — it’s just a heuristic — this will still work even\n//! if no aliases are being used!\n//!\n//! Finally, this isn’t just useful when options could override each other.\n//! Creating an alias `exal=\"exa --long --inode --header\"` then invoking `exal\n//! --grid --long` shouldn’t complain about `--long` being given twice when\n//! it’s clear what the user wants.\n\n\nuse std::ffi::OsStr;\n\nuse crate::fs::dir_action::DirAction;\nuse crate::fs::filter::{FileFilter, GitIgnore};\nuse crate::output::{View, Mode, details, grid_details};\nuse crate::theme::Options as ThemeOptions;\n\nmod dir_action;\nmod file_name;\nmod filter;\nmod flags;\nmod theme;\nmod view;\n\nmod error;\npub use self::error::{OptionsError, NumberSource};\n\nmod help;\nuse self::help::HelpString;\n\nmod parser;\nuse self::parser::MatchedFlags;\n\npub mod vars;\npub use self::vars::Vars;\n\nmod version;\nuse self::version::VersionString;\n\n\n/// These **options** represent a parsed, error-checked versions of the\n/// user’s command-line options.\n#[derive(Debug)]\npub struct Options {\n\n    /// The action to perform when encountering a directory rather than a\n    /// regular file.\n    pub dir_action: DirAction,\n\n    /// How to sort and filter files before outputting them.\n    pub filter: FileFilter,\n\n    /// The user’s preference of view to use (lines, grid, details, or\n    /// grid-details) along with the options on how to render file names.\n    /// If the view requires the terminal to have a width, and there is no\n    /// width, then the view will be downgraded.\n    pub view: View,\n\n    /// The options to make up the styles of the UI and file names.\n    pub theme: ThemeOptions,\n}\n\nimpl Options {\n\n    /// Parse the given iterator of command-line strings into an Options\n    /// struct and a list of free filenames, using the environment variables\n    /// for extra options.\n    #[allow(unused_results)]\n    pub fn parse<'args, I, V>(args: I, vars: &V) -> OptionsResult<'args>\n    where I: IntoIterator<Item = &'args OsStr>,\n          V: Vars,\n    {\n        use crate::options::parser::{Matches, Strictness};\n\n        let strictness = match vars.get(vars::EXA_STRICT) {\n            None                         => Strictness::UseLastArguments,\n            Some(ref t) if t.is_empty()  => Strictness::UseLastArguments,\n            Some(_)                      => Strictness::ComplainAboutRedundantArguments,\n        };\n\n        let Matches { flags, frees } = match flags::ALL_ARGS.parse(args, strictness) {\n            Ok(m)    => m,\n            Err(pe)  => return OptionsResult::InvalidOptions(OptionsError::Parse(pe)),\n        };\n\n        if let Some(help) = HelpString::deduce(&flags) {\n            return OptionsResult::Help(help);\n        }\n\n        if let Some(version) = VersionString::deduce(&flags) {\n            return OptionsResult::Version(version);\n        }\n\n        match Self::deduce(&flags, vars) {\n            Ok(options)  => OptionsResult::Ok(options, frees),\n            Err(oe)      => OptionsResult::InvalidOptions(oe),\n        }\n    }\n\n    /// Whether the View specified in this set of options includes a Git\n    /// status column. It’s only worth trying to discover a repository if the\n    /// results will end up being displayed.\n    pub fn should_scan_for_git(&self) -> bool {\n        if self.filter.git_ignore == GitIgnore::CheckAndIgnore {\n            return true;\n        }\n\n        match self.view.mode {\n            Mode::Details(details::Options { table: Some(ref table), .. }) |\n            Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => table.columns.git,\n            _ => false,\n        }\n    }\n\n    /// Determines the complete set of options based on the given command-line\n    /// arguments, after they’ve been parsed.\n    fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        if cfg!(not(feature = \"git\")) &&\n                matches.has_where_any(|f| f.matches(&flags::GIT) || f.matches(&flags::GIT_IGNORE)).is_some() {\n            return Err(OptionsError::Unsupported(String::from(\n                \"Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa\"\n            )));\n        }\n\n        let view = View::deduce(matches, vars)?;\n        let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;\n        let filter = FileFilter::deduce(matches)?;\n        let theme = ThemeOptions::deduce(matches, vars)?;\n\n        Ok(Self { dir_action, filter, view, theme })\n    }\n}\n\n\n/// The result of the `Options::getopts` function.\n#[derive(Debug)]\npub enum OptionsResult<'args> {\n\n    /// The options were parsed successfully.\n    Ok(Options, Vec<&'args OsStr>),\n\n    /// There was an error parsing the arguments.\n    InvalidOptions(OptionsError),\n\n    /// One of the arguments was `--help`, so display help.\n    Help(HelpString),\n\n    /// One of the arguments was `--version`, so display the version number.\n    Version(VersionString),\n}\n\n\n#[cfg(test)]\npub mod test {\n    use crate::options::parser::{Arg, MatchedFlags};\n    use std::ffi::OsStr;\n\n    #[derive(PartialEq, Eq, Debug)]\n    pub enum Strictnesses {\n        Last,\n        Complain,\n        Both,\n    }\n\n    /// This function gets used by the other testing modules.\n    /// It can run with one or both strictness values: if told to run with\n    /// both, then both should resolve to the same result.\n    ///\n    /// It returns a vector with one or two elements in.\n    /// These elements can then be tested with `assert_eq` or what have you.\n    pub fn parse_for_test<T, F>(inputs: &[&str], args: &'static [&'static Arg], strictnesses: Strictnesses, get: F) -> Vec<T>\n    where F: Fn(&MatchedFlags<'_>) -> T\n    {\n        use self::Strictnesses::*;\n        use crate::options::parser::{Args, Strictness};\n\n        let bits = inputs.iter().map(OsStr::new).collect::<Vec<_>>();\n        let mut result = Vec::new();\n\n        if strictnesses == Last || strictnesses == Both {\n            let results = Args(args).parse(bits.clone(), Strictness::UseLastArguments);\n            result.push(get(&results.unwrap().flags));\n        }\n\n        if strictnesses == Complain || strictnesses == Both {\n            let results = Args(args).parse(bits, Strictness::ComplainAboutRedundantArguments);\n            result.push(get(&results.unwrap().flags));\n        }\n\n        result\n    }\n}\n"
  },
  {
    "path": "src/options/parser.rs",
    "content": "//! A general parser for command-line options.\n//!\n//! exa uses its own hand-rolled parser for command-line options. It supports\n//! the following syntax:\n//!\n//! - Long options: `--inode`, `--grid`\n//! - Long options with values: `--sort size`, `--level=4`\n//! - Short options: `-i`, `-G`\n//! - Short options with values: `-ssize`, `-L=4`\n//!\n//! These values can be mixed and matched: `exa -lssize --grid`. If you’ve used\n//! other command-line programs, then hopefully it’ll work much like them.\n//!\n//! Because exa already has its own files for the help text, shell completions,\n//! man page, and readme, so it can get away with having the options parser do\n//! very little: all it really needs to do is parse a slice of strings.\n//!\n//!\n//! ## UTF-8 and `OsStr`\n//!\n//! The parser uses `OsStr` as its string type. This is necessary for exa to\n//! list files that have invalid UTF-8 in their names: by treating file paths\n//! as bytes with no encoding, a file can be specified on the command-line and\n//! be looked up without having to be encoded into a `str` first.\n//!\n//! It also avoids the overhead of checking for invalid UTF-8 when parsing\n//! command-line options, as all the options and their values (such as\n//! `--sort size`) are guaranteed to just be 8-bit ASCII.\n\n\nuse std::ffi::{OsStr, OsString};\nuse std::fmt;\n\nuse crate::options::error::{OptionsError, Choices};\n\n\n/// A **short argument** is a single ASCII character.\npub type ShortArg = u8;\n\n/// A **long argument** is a string. This can be a UTF-8 string, even though\n/// the arguments will all be unchecked `OsString` values, because we don’t\n/// actually store the user’s input after it’s been matched to a flag, we just\n/// store which flag it was.\npub type LongArg = &'static str;\n\n/// A **list of values** that an option can have, to be displayed when the\n/// user enters an invalid one or skips it.\n///\n/// This is literally just help text, and won’t be used to validate a value to\n/// see if it’s correct.\npub type Values = &'static [&'static str];\n\n/// A **flag** is either of the two argument types, because they have to\n/// be in the same array together.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum Flag {\n    Short(ShortArg),\n    Long(LongArg),\n}\n\nimpl Flag {\n    pub fn matches(&self, arg: &Arg) -> bool {\n        match self {\n            Self::Short(short)  => arg.short == Some(*short),\n            Self::Long(long)    => arg.long == *long,\n        }\n    }\n}\n\nimpl fmt::Display for Flag {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        match self {\n            Self::Short(short)  => write!(f, \"-{}\", *short as char),\n            Self::Long(long)    => write!(f, \"--{}\", long),\n        }\n    }\n}\n\n/// Whether redundant arguments should be considered a problem.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum Strictness {\n\n    /// Throw an error when an argument doesn’t do anything, either because\n    /// it requires another argument to be specified, or because two conflict.\n    ComplainAboutRedundantArguments,\n\n    /// Search the arguments list back-to-front, giving ones specified later\n    /// in the list priority over earlier ones.\n    UseLastArguments,\n}\n\n/// Whether a flag takes a value. This is applicable to both long and short\n/// arguments.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum TakesValue {\n\n    /// This flag has to be followed by a value.\n    /// If there’s a fixed set of possible values, they can be printed out\n    /// with the error text.\n    Necessary(Option<Values>),\n\n    /// This flag will throw an error if there’s a value after it.\n    Forbidden,\n\n    /// This flag may be followed by a value to override its defaults\n    Optional(Option<Values>),\n}\n\n\n/// An **argument** can be matched by one of the user’s input strings.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub struct Arg {\n\n    /// The short argument that matches it, if any.\n    pub short: Option<ShortArg>,\n\n    /// The long argument that matches it. This is non-optional; all flags\n    /// should at least have a descriptive long name.\n    pub long: LongArg,\n\n    /// Whether this flag takes a value or not.\n    pub takes_value: TakesValue,\n}\n\nimpl fmt::Display for Arg {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        write!(f, \"--{}\", self.long)?;\n\n        if let Some(short) = self.short {\n            write!(f, \" (-{})\", short as char)?;\n        }\n\n        Ok(())\n    }\n}\n\n\n/// Literally just several args.\n#[derive(PartialEq, Eq, Debug)]\npub struct Args(pub &'static [&'static Arg]);\n\nimpl Args {\n\n    /// Iterates over the given list of command-line arguments and parses\n    /// them into a list of matched flags and free strings.\n    pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>\n    where I: IntoIterator<Item = &'args OsStr>\n    {\n        let mut parsing = true;\n\n        // The results that get built up.\n        let mut result_flags = Vec::new();\n        let mut frees: Vec<&OsStr> = Vec::new();\n\n        // Iterate over the inputs with “while let” because we need to advance\n        // the iterator manually whenever an argument that takes a value\n        // doesn’t have one in its string so it needs the next one.\n        let mut inputs = inputs.into_iter();\n        while let Some(arg) = inputs.next() {\n            let bytes = os_str_to_bytes(arg);\n\n            // Stop parsing if one of the arguments is the literal string “--”.\n            // This allows a file named “--arg” to be specified by passing in\n            // the pair “-- --arg”, without it getting matched as a flag that\n            // doesn’t exist.\n            if ! parsing {\n                frees.push(arg)\n            }\n            else if arg == \"--\" {\n                parsing = false;\n            }\n\n            // If the string starts with *two* dashes then it’s a long argument.\n            else if bytes.starts_with(b\"--\") {\n                let long_arg_name = bytes_to_os_str(&bytes[2..]);\n\n                // If there’s an equals in it, then the string before the\n                // equals will be the flag’s name, and the string after it\n                // will be its value.\n                if let Some((before, after)) = split_on_equals(long_arg_name) {\n                    let arg = self.lookup_long(before)?;\n                    let flag = Flag::Long(arg.long);\n                    match arg.takes_value {\n                        TakesValue::Necessary(_) |\n                        TakesValue::Optional(_)  => result_flags.push((flag, Some(after))),\n                        TakesValue::Forbidden    => return Err(ParseError::ForbiddenValue { flag }),\n                    }\n                }\n\n                // If there’s no equals, then the entire string (apart from\n                // the dashes) is the argument name.\n                else {\n                    let arg = self.lookup_long(long_arg_name)?;\n                    let flag = Flag::Long(arg.long);\n                    match arg.takes_value {\n                        TakesValue::Forbidden => {\n                            result_flags.push((flag, None))\n                        }\n                        TakesValue::Necessary(values) => {\n                            if let Some(next_arg) = inputs.next() {\n                                result_flags.push((flag, Some(next_arg)));\n                            }\n                            else {\n                                return Err(ParseError::NeedsValue { flag, values })\n                            }\n                        }\n                        TakesValue::Optional(_) => {\n                            if let Some(next_arg) = inputs.next() {\n                                result_flags.push((flag, Some(next_arg)));\n                            }\n                            else {\n                                result_flags.push((flag, None));\n                            }\n                        }\n                    }\n                }\n            }\n\n            // If the string starts with *one* dash then it’s one or more\n            // short arguments.\n            else if bytes.starts_with(b\"-\") && arg != \"-\" {\n                let short_arg = bytes_to_os_str(&bytes[1..]);\n\n                // If there’s an equals in it, then the argument immediately\n                // before the equals was the one that has the value, with the\n                // others (if any) as value-less short ones.\n                //\n                //   -x=abc         => ‘x=abc’\n                //   -abcdx=fgh     => ‘a’, ‘b’, ‘c’, ‘d’, ‘x=fgh’\n                //   -x=            =>  error\n                //   -abcdx=        =>  error\n                //\n                // There’s no way to give two values in a cluster like this:\n                // it’s an error if any of the first set of arguments actually\n                // takes a value.\n                if let Some((before, after)) = split_on_equals(short_arg) {\n                    let (arg_with_value, other_args) = os_str_to_bytes(before).split_last().unwrap();\n\n                    // Process the characters immediately following the dash...\n                    for byte in other_args {\n                        let arg = self.lookup_short(*byte)?;\n                        let flag = Flag::Short(*byte);\n                        match arg.takes_value {\n                            TakesValue::Forbidden |\n                            TakesValue::Optional(_)  => {\n                                result_flags.push((flag, None));\n                            }\n                            TakesValue::Necessary(values) => {\n                                return Err(ParseError::NeedsValue { flag, values });\n                            }\n                        }\n                    }\n\n                    // ...then the last one and the value after the equals.\n                    let arg = self.lookup_short(*arg_with_value)?;\n                    let flag = Flag::Short(arg.short.unwrap());\n                    match arg.takes_value {\n                        TakesValue::Necessary(_) |\n                        TakesValue::Optional(_)  => {\n                            result_flags.push((flag, Some(after)));\n                        }\n                        TakesValue::Forbidden => {\n                            return Err(ParseError::ForbiddenValue { flag });\n                        }\n                    }\n                }\n\n                // If there’s no equals, then every character is parsed as\n                // its own short argument. However, if any of the arguments\n                // takes a value, then the *rest* of the string is used as\n                // its value, and if there’s no rest of the string, then it\n                // uses the next one in the iterator.\n                //\n                //   -a        => ‘a’\n                //   -abc      => ‘a’, ‘b’, ‘c’\n                //   -abxdef   => ‘a’, ‘b’, ‘x=def’\n                //   -abx def  => ‘a’, ‘b’, ‘x=def’\n                //   -abx      =>  error\n                //\n                else {\n                    for (index, byte) in bytes.iter().enumerate().skip(1) {\n                        let arg = self.lookup_short(*byte)?;\n                        let flag = Flag::Short(*byte);\n                        match arg.takes_value {\n                            TakesValue::Forbidden => {\n                                result_flags.push((flag, None))\n                            }\n                            TakesValue::Necessary(values) |\n                            TakesValue::Optional(values) => {\n                                if index < bytes.len() - 1 {\n                                    let remnants = &bytes[index+1 ..];\n                                    result_flags.push((flag, Some(bytes_to_os_str(remnants))));\n                                    break;\n                                }\n                                else if let Some(next_arg) = inputs.next() {\n                                    result_flags.push((flag, Some(next_arg)));\n                                }\n                                else {\n                                    match arg.takes_value {\n                                        TakesValue::Forbidden => {\n                                            unreachable!()\n                                        }\n                                        TakesValue::Necessary(_) => {\n                                            return Err(ParseError::NeedsValue { flag, values });\n                                        }\n                                        TakesValue::Optional(_) => {\n                                            result_flags.push((flag, None));\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            // Otherwise, it’s a free string, usually a file name.\n            else {\n                frees.push(arg)\n            }\n        }\n\n        Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness } })\n    }\n\n    fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {\n        match self.0.iter().find(|arg| arg.short == Some(short)) {\n            Some(arg)  => Ok(arg),\n            None       => Err(ParseError::UnknownShortArgument { attempt: short })\n        }\n    }\n\n    fn lookup_long<'b>(&self, long: &'b OsStr) -> Result<&Arg, ParseError> {\n        match self.0.iter().find(|arg| arg.long == long) {\n            Some(arg)  => Ok(arg),\n            None       => Err(ParseError::UnknownArgument { attempt: long.to_os_string() })\n        }\n    }\n}\n\n\n/// The **matches** are the result of parsing the user’s command-line strings.\n#[derive(PartialEq, Eq, Debug)]\npub struct Matches<'args> {\n\n    /// The flags that were parsed from the user’s input.\n    pub flags: MatchedFlags<'args>,\n\n    /// All the strings that weren’t matched as arguments, as well as anything\n    /// after the special “--” string.\n    pub frees: Vec<&'args OsStr>,\n}\n\n#[derive(PartialEq, Eq, Debug)]\npub struct MatchedFlags<'args> {\n\n    /// The individual flags from the user’s input, in the order they were\n    /// originally given.\n    ///\n    /// Long and short arguments need to be kept in the same vector because\n    /// we usually want the one nearest the end to count, and to know this,\n    /// we need to know where they are in relation to one another.\n    flags: Vec<(Flag, Option<&'args OsStr>)>,\n\n    /// Whether to check for duplicate or redundant arguments.\n    strictness: Strictness,\n}\n\nimpl<'a> MatchedFlags<'a> {\n\n    /// Whether the given argument was specified.\n    /// Returns `true` if it was, `false` if it wasn’t, and an error in\n    /// strict mode if it was specified more than once.\n    pub fn has(&self, arg: &'static Arg) -> Result<bool, OptionsError> {\n        self.has_where(|flag| flag.matches(arg))\n            .map(|flag| flag.is_some())\n    }\n\n    /// Returns the first found argument that satisfies the predicate, or\n    /// nothing if none is found, or an error in strict mode if multiple\n    /// argument satisfy the predicate.\n    ///\n    /// You’ll have to test the resulting flag to see which argument it was.\n    pub fn has_where<P>(&self, predicate: P) -> Result<Option<&Flag>, OptionsError>\n    where P: Fn(&Flag) -> bool {\n        if self.is_strict() {\n            let all = self.flags.iter()\n                          .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0))\n                          .collect::<Vec<_>>();\n\n            if all.len() < 2 { Ok(all.first().map(|t| &t.0)) }\n                        else { Err(OptionsError::Duplicate(all[0].0, all[1].0)) }\n        }\n        else {\n            Ok(self.has_where_any(predicate))\n        }\n    }\n\n    /// Returns the first found argument that satisfies the predicate, or\n    /// nothing if none is found, with strict mode having no effect.\n    ///\n    /// You’ll have to test the resulting flag to see which argument it was.\n    pub fn has_where_any<P>(&self, predicate: P) -> Option<&Flag>\n    where P: Fn(&Flag) -> bool {\n        self.flags.iter().rev()\n            .find(|tuple| tuple.1.is_none() && predicate(&tuple.0))\n            .map(|tuple| &tuple.0)\n    }\n\n    // This code could probably be better.\n    // Both ‘has’ and ‘get’ immediately begin with a conditional, which makes\n    // me think the functionality could be moved to inside Strictness.\n\n    /// Returns the value of the given argument if it was specified, nothing\n    /// if it wasn’t, and an error in strict mode if it was specified more\n    /// than once.\n    pub fn get(&self, arg: &'static Arg) -> Result<Option<&OsStr>, OptionsError> {\n        self.get_where(|flag| flag.matches(arg))\n    }\n\n    /// Returns the value of the argument that matches the predicate if it\n    /// was specified, nothing if it wasn’t, and an error in strict mode if\n    /// multiple arguments matched the predicate.\n    ///\n    /// It’s not possible to tell which flag the value belonged to from this.\n    pub fn get_where<P>(&self, predicate: P) -> Result<Option<&OsStr>, OptionsError>\n    where P: Fn(&Flag) -> bool {\n        if self.is_strict() {\n            let those = self.flags.iter()\n                            .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0))\n                            .collect::<Vec<_>>();\n\n            if those.len() < 2 { Ok(those.first().copied().map(|t| t.1.unwrap())) }\n                          else { Err(OptionsError::Duplicate(those[0].0, those[1].0)) }\n        }\n        else {\n            let found = self.flags.iter().rev()\n                            .find(|tuple| tuple.1.is_some() && predicate(&tuple.0))\n                            .map(|tuple| tuple.1.unwrap());\n            Ok(found)\n        }\n    }\n\n    // It’s annoying that ‘has’ and ‘get’ won’t work when accidentally given\n    // flags that do/don’t take values, but this should be caught by tests.\n\n    /// Counts the number of occurrences of the given argument, even in\n    /// strict mode.\n    pub fn count(&self, arg: &Arg) -> usize {\n        self.flags.iter()\n            .filter(|tuple| tuple.0.matches(arg))\n            .count()\n    }\n\n    /// Checks whether strict mode is on. This is usually done from within\n    /// ‘has’ and ‘get’, but it’s available in an emergency.\n    pub fn is_strict(&self) -> bool {\n        self.strictness == Strictness::ComplainAboutRedundantArguments\n    }\n}\n\n\n/// A problem with the user’s input that meant it couldn’t be parsed into a\n/// coherent list of arguments.\n#[derive(PartialEq, Eq, Debug)]\npub enum ParseError {\n\n    /// A flag that has to take a value was not given one.\n    NeedsValue { flag: Flag, values: Option<Values> },\n\n    /// A flag that can’t take a value *was* given one.\n    ForbiddenValue { flag: Flag },\n\n    /// A short argument, either alone or in a cluster, was not\n    /// recognised by the program.\n    UnknownShortArgument { attempt: ShortArg },\n\n    /// A long argument was not recognised by the program.\n    /// We don’t have a known &str version of the flag, so\n    /// this may not be valid UTF-8.\n    UnknownArgument { attempt: OsString },\n}\n\nimpl fmt::Display for ParseError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::NeedsValue { flag, values: None }      => write!(f, \"Flag {} needs a value\", flag),\n            Self::NeedsValue { flag, values: Some(cs) }  => write!(f, \"Flag {} needs a value ({})\", flag, Choices(cs)),\n            Self::ForbiddenValue { flag }                => write!(f, \"Flag {} cannot take a value\", flag),\n            Self::UnknownShortArgument { attempt }       => write!(f, \"Unknown argument -{}\", *attempt as char),\n            Self::UnknownArgument { attempt }            => write!(f, \"Unknown argument --{}\", attempt.to_string_lossy()),\n        }\n    }\n}\n\n#[cfg(unix)]\nfn os_str_to_bytes<'b>(s: &'b OsStr) ->  &'b [u8]{\n    use std::os::unix::ffi::OsStrExt;\n\n    return s.as_bytes()\n}\n\n#[cfg(unix)]\nfn bytes_to_os_str<'b>(b:  &'b [u8]) ->  &'b OsStr{\n    use std::os::unix::ffi::OsStrExt;\n\n    return OsStr::from_bytes(b);\n}\n\n#[cfg(windows)]\nfn os_str_to_bytes<'b>(s: &'b OsStr) ->  &'b [u8]{\n    return s.to_str().unwrap().as_bytes()\n}\n\n#[cfg(windows)]\nfn bytes_to_os_str<'b>(b:  &'b [u8]) ->  &'b OsStr{\n    use std::str;\n\n    return OsStr::new(str::from_utf8(b).unwrap());\n}\n\n/// Splits a string on its `=` character, returning the two substrings on\n/// either side. Returns `None` if there’s no equals or a string is missing.\nfn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {\n    if let Some(index) = os_str_to_bytes(input).iter().position(|elem| *elem == b'=') {\n        let (before, after) = os_str_to_bytes(input).split_at(index);\n\n        // The after string contains the = that we need to remove.\n        if ! before.is_empty() && after.len() >= 2 {\n            return Some((bytes_to_os_str(before),\n                         bytes_to_os_str(&after[1..])))\n        }\n    }\n\n    None\n}\n\n\n#[cfg(test)]\nmod split_test {\n    use super::split_on_equals;\n    use std::ffi::{OsStr, OsString};\n\n    macro_rules! test_split {\n        ($name:ident: $input:expr => None) => {\n            #[test]\n            fn $name() {\n                assert_eq!(split_on_equals(&OsString::from($input)),\n                           None);\n            }\n        };\n\n        ($name:ident: $input:expr => $before:expr, $after:expr) => {\n            #[test]\n            fn $name() {\n                assert_eq!(split_on_equals(&OsString::from($input)),\n                           Some((OsStr::new($before), OsStr::new($after))));\n            }\n        };\n    }\n\n    test_split!(empty:   \"\"   => None);\n    test_split!(letter:  \"a\"  => None);\n\n    test_split!(just:      \"=\"    => None);\n    test_split!(intro:     \"=bbb\" => None);\n    test_split!(denou:  \"aaa=\"    => None);\n    test_split!(equals: \"aaa=bbb\" => \"aaa\", \"bbb\");\n\n    test_split!(sort: \"--sort=size\"     => \"--sort\", \"size\");\n    test_split!(more: \"this=that=other\" => \"this\",   \"that=other\");\n}\n\n\n#[cfg(test)]\nmod parse_test {\n    use super::*;\n\n    macro_rules! test {\n        ($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => {\n            #[test]\n            fn $name() {\n\n                let inputs: &[&'static str] = $inputs.as_ref();\n                let inputs = inputs.iter().map(OsStr::new);\n\n                let frees: &[&'static str] = $frees.as_ref();\n                let frees  = frees.iter().map(OsStr::new).collect();\n\n                let flags = <[_]>::into_vec(Box::new($flags));\n\n                let strictness = Strictness::UseLastArguments;  // this isn’t even used\n                let got = Args(TEST_ARGS).parse(inputs, strictness);\n                let flags = MatchedFlags { flags, strictness };\n\n                let expected = Ok(Matches { frees, flags });\n                assert_eq!(got, expected);\n            }\n        };\n\n        ($name:ident: $inputs:expr => error $error:expr) => {\n            #[test]\n            fn $name() {\n                use self::ParseError::*;\n\n                let inputs = $inputs.iter().map(OsStr::new);\n\n                let strictness = Strictness::UseLastArguments;  // this isn’t even used\n                let got = Args(TEST_ARGS).parse(inputs, strictness);\n                assert_eq!(got, Err($error));\n            }\n        };\n    }\n\n    const SUGGESTIONS: Values = &[ \"example\" ];\n\n    static TEST_ARGS: &[&Arg] = &[\n        &Arg { short: Some(b'l'), long: \"long\",     takes_value: TakesValue::Forbidden },\n        &Arg { short: Some(b'v'), long: \"verbose\",  takes_value: TakesValue::Forbidden },\n        &Arg { short: Some(b'c'), long: \"count\",    takes_value: TakesValue::Necessary(None) },\n        &Arg { short: Some(b't'), long: \"type\",     takes_value: TakesValue::Necessary(Some(SUGGESTIONS)) }\n    ];\n\n\n    // Just filenames\n    test!(empty:       []       => frees: [],         flags: []);\n    test!(one_arg:     [\"exa\"]  => frees: [ \"exa\" ],  flags: []);\n\n    // Dashes and double dashes\n    test!(one_dash:    [\"-\"]             => frees: [ \"-\" ],       flags: []);\n    test!(two_dashes:  [\"--\"]            => frees: [],            flags: []);\n    test!(two_file:    [\"--\", \"file\"]    => frees: [ \"file\" ],    flags: []);\n    test!(two_arg_l:   [\"--\", \"--long\"]  => frees: [ \"--long\" ],  flags: []);\n    test!(two_arg_s:   [\"--\", \"-l\"]      => frees: [ \"-l\" ],      flags: []);\n\n\n    // Long args\n    test!(long:        [\"--long\"]               => frees: [],       flags: [ (Flag::Long(\"long\"), None) ]);\n    test!(long_then:   [\"--long\", \"4\"]          => frees: [ \"4\" ],  flags: [ (Flag::Long(\"long\"), None) ]);\n    test!(long_two:    [\"--long\", \"--verbose\"]  => frees: [],       flags: [ (Flag::Long(\"long\"), None), (Flag::Long(\"verbose\"), None) ]);\n\n    // Long args with values\n    test!(bad_equals:  [\"--long=equals\"]  => error ForbiddenValue { flag: Flag::Long(\"long\") });\n    test!(no_arg:      [\"--count\"]        => error NeedsValue     { flag: Flag::Long(\"count\"), values: None });\n    test!(arg_equals:  [\"--count=4\"]      => frees: [],  flags: [ (Flag::Long(\"count\"), Some(OsStr::new(\"4\"))) ]);\n    test!(arg_then:    [\"--count\", \"4\"]   => frees: [],  flags: [ (Flag::Long(\"count\"), Some(OsStr::new(\"4\"))) ]);\n\n    // Long args with values and suggestions\n    test!(no_arg_s:      [\"--type\"]         => error NeedsValue { flag: Flag::Long(\"type\"), values: Some(SUGGESTIONS) });\n    test!(arg_equals_s:  [\"--type=exa\"]     => frees: [],  flags: [ (Flag::Long(\"type\"), Some(OsStr::new(\"exa\"))) ]);\n    test!(arg_then_s:    [\"--type\", \"exa\"]  => frees: [],  flags: [ (Flag::Long(\"type\"), Some(OsStr::new(\"exa\"))) ]);\n\n\n    // Short args\n    test!(short:       [\"-l\"]            => frees: [],       flags: [ (Flag::Short(b'l'), None) ]);\n    test!(short_then:  [\"-l\", \"4\"]       => frees: [ \"4\" ],  flags: [ (Flag::Short(b'l'), None) ]);\n    test!(short_two:   [\"-lv\"]           => frees: [],       flags: [ (Flag::Short(b'l'), None), (Flag::Short(b'v'), None) ]);\n    test!(mixed:       [\"-v\", \"--long\"]  => frees: [],       flags: [ (Flag::Short(b'v'), None), (Flag::Long(\"long\"), None) ]);\n\n    // Short args with values\n    test!(bad_short:          [\"-l=equals\"]   => error ForbiddenValue { flag: Flag::Short(b'l') });\n    test!(short_none:         [\"-c\"]          => error NeedsValue     { flag: Flag::Short(b'c'), values: None });\n    test!(short_arg_eq:       [\"-c=4\"]        => frees: [],  flags: [(Flag::Short(b'c'), Some(OsStr::new(\"4\"))) ]);\n    test!(short_arg_then:     [\"-c\", \"4\"]     => frees: [],  flags: [(Flag::Short(b'c'), Some(OsStr::new(\"4\"))) ]);\n    test!(short_two_together: [\"-lctwo\"]      => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new(\"two\"))) ]);\n    test!(short_two_equals:   [\"-lc=two\"]     => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new(\"two\"))) ]);\n    test!(short_two_next:     [\"-lc\", \"two\"]  => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new(\"two\"))) ]);\n\n    // Short args with values and suggestions\n    test!(short_none_s:         [\"-t\"]         => error NeedsValue { flag: Flag::Short(b't'), values: Some(SUGGESTIONS) });\n    test!(short_two_together_s: [\"-texa\"]      => frees: [],  flags: [(Flag::Short(b't'), Some(OsStr::new(\"exa\"))) ]);\n    test!(short_two_equals_s:   [\"-t=exa\"]     => frees: [],  flags: [(Flag::Short(b't'), Some(OsStr::new(\"exa\"))) ]);\n    test!(short_two_next_s:     [\"-t\", \"exa\"]  => frees: [],  flags: [(Flag::Short(b't'), Some(OsStr::new(\"exa\"))) ]);\n\n\n    // Unknown args\n    test!(unknown_long:          [\"--quiet\"]      => error UnknownArgument      { attempt: OsString::from(\"quiet\") });\n    test!(unknown_long_eq:       [\"--quiet=shhh\"] => error UnknownArgument      { attempt: OsString::from(\"quiet\") });\n    test!(unknown_short:         [\"-q\"]           => error UnknownShortArgument { attempt: b'q' });\n    test!(unknown_short_2nd:     [\"-lq\"]          => error UnknownShortArgument { attempt: b'q' });\n    test!(unknown_short_eq:      [\"-q=shhh\"]      => error UnknownShortArgument { attempt: b'q' });\n    test!(unknown_short_2nd_eq:  [\"-lq=shhh\"]     => error UnknownShortArgument { attempt: b'q' });\n}\n\n\n#[cfg(test)]\nmod matches_test {\n    use super::*;\n\n    macro_rules! test {\n        ($name:ident: $input:expr, has $param:expr => $result:expr) => {\n            #[test]\n            fn $name() {\n                let flags = MatchedFlags {\n                    flags: $input.to_vec(),\n                    strictness: Strictness::UseLastArguments,\n                };\n\n                assert_eq!(flags.has(&$param), Ok($result));\n            }\n        };\n    }\n\n    static VERBOSE: Arg = Arg { short: Some(b'v'), long: \"verbose\", takes_value: TakesValue::Forbidden };\n    static COUNT:   Arg = Arg { short: Some(b'c'), long: \"count\",   takes_value: TakesValue::Necessary(None) };\n\n\n    test!(short_never:  [],                                                              has VERBOSE => false);\n    test!(short_once:   [(Flag::Short(b'v'), None)],                                     has VERBOSE => true);\n    test!(short_twice:  [(Flag::Short(b'v'), None), (Flag::Short(b'v'), None)],          has VERBOSE => true);\n    test!(long_once:    [(Flag::Long(\"verbose\"), None)],                                 has VERBOSE => true);\n    test!(long_twice:   [(Flag::Long(\"verbose\"), None), (Flag::Long(\"verbose\"), None)],  has VERBOSE => true);\n    test!(long_mixed:   [(Flag::Long(\"verbose\"), None), (Flag::Short(b'v'), None)],      has VERBOSE => true);\n\n\n    #[test]\n    fn only_count() {\n        let everything = OsString::from(\"everything\");\n\n        let flags = MatchedFlags {\n            flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ],\n            strictness: Strictness::UseLastArguments,\n        };\n\n        assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));\n    }\n\n    #[test]\n    fn rightmost_count() {\n        let everything = OsString::from(\"everything\");\n        let nothing    = OsString::from(\"nothing\");\n\n        let flags = MatchedFlags {\n            flags: vec![ (Flag::Short(b'c'), Some(&*everything)),\n                         (Flag::Short(b'c'), Some(&*nothing)) ],\n            strictness: Strictness::UseLastArguments,\n        };\n\n        assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing)));\n    }\n\n    #[test]\n    fn no_count() {\n        let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };\n\n        assert!(!flags.has(&COUNT).unwrap());\n    }\n}\n"
  },
  {
    "path": "src/options/theme.rs",
    "content": "use crate::options::{flags, vars, Vars, OptionsError};\nuse crate::options::parser::MatchedFlags;\nuse crate::theme::{Options, UseColours, ColourScale, Definitions};\n\n\nimpl Options {\n    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        let use_colours = UseColours::deduce(matches, vars)?;\n        let colour_scale = ColourScale::deduce(matches)?;\n\n        let definitions = if use_colours == UseColours::Never {\n                Definitions::default()\n            }\n            else {\n                Definitions::deduce(vars)\n            };\n\n        Ok(Self { use_colours, colour_scale, definitions })\n    }\n}\n\n\nimpl UseColours {\n    fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        let default_value = match vars.get(vars::NO_COLOR) {\n            Some(_) => Self::Never,\n            None => Self::Automatic,\n        };\n\n        let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {\n            Some(w)  => w,\n            None => return Ok(default_value),\n        };\n\n        if word == \"always\" {\n            Ok(Self::Always)\n        }\n        else if word == \"auto\" || word == \"automatic\" {\n            Ok(Self::Automatic)\n        }\n        else if word == \"never\" {\n            Ok(Self::Never)\n        }\n        else {\n            Err(OptionsError::BadArgument(&flags::COLOR, word.into()))\n        }\n    }\n}\n\n\nimpl ColourScale {\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        if matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?.is_some() {\n            Ok(Self::Gradient)\n        }\n        else {\n            Ok(Self::Fixed)\n        }\n    }\n}\n\n\nimpl Definitions {\n    fn deduce<V: Vars>(vars: &V) -> Self {\n        let ls =  vars.get(vars::LS_COLORS) .map(|e| e.to_string_lossy().to_string());\n        let exa = vars.get(vars::EXA_COLORS).map(|e| e.to_string_lossy().to_string());\n        Self { ls, exa }\n    }\n}\n\n\n#[cfg(test)]\nmod terminal_test {\n    use super::*;\n    use std::ffi::OsString;\n    use crate::options::flags;\n    use crate::options::parser::{Flag, Arg};\n\n    use crate::options::test::parse_for_test;\n    use crate::options::test::Strictnesses::*;\n\n    static TEST_ARGS: &[&Arg] = &[ &flags::COLOR,       &flags::COLOUR,\n                                   &flags::COLOR_SCALE, &flags::COLOUR_SCALE, ];\n\n    macro_rules! test {\n        ($name:ident:  $type:ident <- $inputs:expr;  $stricts:expr => $result:expr) => {\n            #[test]\n            fn $name() {\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {\n                    assert_eq!(result, $result);\n                }\n            }\n        };\n\n        ($name:ident:  $type:ident <- $inputs:expr, $env:expr;  $stricts:expr => $result:expr) => {\n            #[test]\n            fn $name() {\n                let env = $env;\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &env)) {\n                    assert_eq!(result, $result);\n                }\n            }\n        };\n\n        ($name:ident:  $type:ident <- $inputs:expr;  $stricts:expr => err $result:expr) => {\n            #[test]\n            fn $name() {\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {\n                    assert_eq!(result.unwrap_err(), $result);\n                }\n            }\n        };\n\n        ($name:ident:  $type:ident <- $inputs:expr, $env:expr;  $stricts:expr => err $result:expr) => {\n            #[test]\n            fn $name() {\n                let env = $env;\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &env)) {\n                    assert_eq!(result.unwrap_err(), $result);\n                }\n            }\n        };\n    }\n\n    struct MockVars {\n        ls: &'static str,\n        exa: &'static str,\n        no_color: &'static str,\n    }\n\n    impl MockVars {\n        fn empty() -> MockVars {\n            MockVars {\n                ls: \"\",\n                exa: \"\",\n                no_color: \"\",\n            }\n        }\n        fn with_no_color() -> MockVars {\n            MockVars {\n                ls: \"\",\n                exa: \"\",\n                no_color: \"true\",\n            }\n        }\n    }\n\n    // Test impl that just returns the value it has.\n    impl Vars for MockVars {\n        fn get(&self, name: &'static str) -> Option<OsString> {\n            if name == vars::LS_COLORS && ! self.ls.is_empty() {\n                Some(OsString::from(self.ls.clone()))\n            }\n            else if name == vars::EXA_COLORS && ! self.exa.is_empty() {\n                Some(OsString::from(self.exa.clone()))\n            }\n            else if name == vars::NO_COLOR && ! self.no_color.is_empty() {\n                Some(OsString::from(self.no_color.clone()))\n            }\n            else {\n                None\n            }\n        }\n    }\n\n\n\n    // Default\n    test!(empty:         UseColours <- [], MockVars::empty();                     Both => Ok(UseColours::Automatic));\n    test!(empty_with_no_color: UseColours <- [], MockVars::with_no_color();             Both => Ok(UseColours::Never));\n\n    // --colour\n    test!(u_always:      UseColours <- [\"--colour=always\"], MockVars::empty();    Both => Ok(UseColours::Always));\n    test!(u_auto:        UseColours <- [\"--colour\", \"auto\"], MockVars::empty();   Both => Ok(UseColours::Automatic));\n    test!(u_never:       UseColours <- [\"--colour=never\"], MockVars::empty();     Both => Ok(UseColours::Never));\n\n    // --color\n    test!(no_u_always:   UseColours <- [\"--color\", \"always\"], MockVars::empty();  Both => Ok(UseColours::Always));\n    test!(no_u_auto:     UseColours <- [\"--color=auto\"], MockVars::empty();       Both => Ok(UseColours::Automatic));\n    test!(no_u_never:    UseColours <- [\"--color\", \"never\"], MockVars::empty();   Both => Ok(UseColours::Never));\n\n    // Errors\n    test!(no_u_error:    UseColours <- [\"--color=upstream\"], MockVars::empty();   Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from(\"upstream\"))); // the error is for --color\n    test!(u_error:       UseColours <- [\"--colour=lovers\"], MockVars::empty();    Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from(\"lovers\"))); // and so is this one!\n\n    // Overriding\n    test!(overridden_1:  UseColours <- [\"--colour=auto\", \"--colour=never\"], MockVars::empty();  Last => Ok(UseColours::Never));\n    test!(overridden_2:  UseColours <- [\"--color=auto\",  \"--colour=never\"], MockVars::empty();  Last => Ok(UseColours::Never));\n    test!(overridden_3:  UseColours <- [\"--colour=auto\", \"--color=never\"], MockVars::empty();   Last => Ok(UseColours::Never));\n    test!(overridden_4:  UseColours <- [\"--color=auto\",  \"--color=never\"], MockVars::empty();   Last => Ok(UseColours::Never));\n\n    test!(overridden_5:  UseColours <- [\"--colour=auto\", \"--colour=never\"], MockVars::empty();  Complain => err OptionsError::Duplicate(Flag::Long(\"colour\"), Flag::Long(\"colour\")));\n    test!(overridden_6:  UseColours <- [\"--color=auto\",  \"--colour=never\"], MockVars::empty();  Complain => err OptionsError::Duplicate(Flag::Long(\"color\"),  Flag::Long(\"colour\")));\n    test!(overridden_7:  UseColours <- [\"--colour=auto\", \"--color=never\"], MockVars::empty();   Complain => err OptionsError::Duplicate(Flag::Long(\"colour\"), Flag::Long(\"color\")));\n    test!(overridden_8:  UseColours <- [\"--color=auto\",  \"--color=never\"], MockVars::empty();   Complain => err OptionsError::Duplicate(Flag::Long(\"color\"),  Flag::Long(\"color\")));\n\n    test!(scale_1:  ColourScale <- [\"--color-scale\", \"--colour-scale\"];   Last => Ok(ColourScale::Gradient));\n    test!(scale_2:  ColourScale <- [\"--color-scale\",                 ];   Last => Ok(ColourScale::Gradient));\n    test!(scale_3:  ColourScale <- [                 \"--colour-scale\"];   Last => Ok(ColourScale::Gradient));\n    test!(scale_4:  ColourScale <- [                                 ];   Last => Ok(ColourScale::Fixed));\n\n    test!(scale_5:  ColourScale <- [\"--color-scale\", \"--colour-scale\"];   Complain => err OptionsError::Duplicate(Flag::Long(\"color-scale\"),  Flag::Long(\"colour-scale\")));\n    test!(scale_6:  ColourScale <- [\"--color-scale\",                 ];   Complain => Ok(ColourScale::Gradient));\n    test!(scale_7:  ColourScale <- [                 \"--colour-scale\"];   Complain => Ok(ColourScale::Gradient));\n    test!(scale_8:  ColourScale <- [                                 ];   Complain => Ok(ColourScale::Fixed));\n}\n"
  },
  {
    "path": "src/options/vars.rs",
    "content": "use std::ffi::OsString;\n\n\n// General variables\n\n/// Environment variable used to colour files, both by their filesystem type\n/// (symlink, socket, directory) and their file name or extension (image,\n/// video, archive);\npub static LS_COLORS: &str = \"LS_COLORS\";\n\n/// Environment variable used to override the width of the terminal, in\n/// characters.\npub static COLUMNS: &str = \"COLUMNS\";\n\n/// Environment variable used to datetime format.\npub static TIME_STYLE: &str = \"TIME_STYLE\";\n\n/// Environment variable used to disable colors.\n/// See: <https://no-color.org/>\npub static NO_COLOR: &str = \"NO_COLOR\";\n\n// exa-specific variables\n\n/// Environment variable used to colour exa’s interface when colours are\n/// enabled. This includes all the colours that `LS_COLORS` would recognise,\n/// overriding them if necessary. It can also contain exa-specific codes.\npub static EXA_COLORS: &str = \"EXA_COLORS\";\n\n/// Environment variable used to switch on strict argument checking, such as\n/// complaining if an argument was specified twice, or if two conflict.\n/// This is meant to be so you don’t accidentally introduce the wrong\n/// behaviour in a script, rather than for general command-line use.\n/// Any non-empty value will turn strict mode on.\npub static EXA_STRICT: &str = \"EXA_STRICT\";\n\n/// Environment variable used to make exa print out debugging information as\n/// it runs. Any non-empty value will turn debug mode on.\npub static EXA_DEBUG: &str = \"EXA_DEBUG\";\n\n/// Environment variable used to limit the grid-details view\n/// (`--grid --long`) so it’s only activated if there’s at least the given\n/// number of rows of output.\npub static EXA_GRID_ROWS: &str = \"EXA_GRID_ROWS\";\n\n/// Environment variable used to specify how many spaces to print between an\n/// icon and its file name. Different terminals display icons differently,\n/// with 1 space bringing them too close together or 2 spaces putting them too\n/// far apart, so this may be necessary depending on how they are shown.\npub static EXA_ICON_SPACING: &str = \"EXA_ICON_SPACING\";\n\n\n/// Mockable wrapper for `std::env::var_os`.\npub trait Vars {\n    fn get(&self, name: &'static str) -> Option<OsString>;\n}\n\n\n// Test impl that just returns the value it has.\n#[cfg(test)]\nimpl Vars for Option<OsString> {\n    fn get(&self, _name: &'static str) -> Option<OsString> {\n        self.clone()\n    }\n}\n"
  },
  {
    "path": "src/options/version.rs",
    "content": "//! Printing the version string.\n//!\n//! The code that works out which string to print is done in `build.rs`.\n\nuse std::fmt;\n\nuse crate::options::flags;\nuse crate::options::parser::MatchedFlags;\n\n\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub struct VersionString;\n// There were options here once, but there aren’t anymore!\n\nimpl VersionString {\n\n    /// Determines how to show the version, if at all, based on the user’s\n    /// command-line arguments. This one works backwards from the other\n    /// ‘deduce’ functions, returning Err if help needs to be shown.\n    ///\n    /// Like --help, this doesn’t check for errors.\n    pub fn deduce(matches: &MatchedFlags<'_>) -> Option<Self> {\n        if matches.count(&flags::VERSION) > 0 {\n            Some(Self)\n        }\n        else {\n            None\n        }\n    }\n}\n\nimpl fmt::Display for VersionString {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        write!(f, \"{}\", include_str!(concat!(env!(\"OUT_DIR\"), \"/version_string.txt\")))\n    }\n}\n\n\n#[cfg(test)]\nmod test {\n    use crate::options::{Options, OptionsResult};\n    use std::ffi::OsStr;\n\n    #[test]\n    fn version() {\n        let args = vec![ OsStr::new(\"--version\") ];\n        let opts = Options::parse(args, &None);\n        assert!(matches!(opts, OptionsResult::Version(_)));\n    }\n\n    #[test]\n    fn version_with_file() {\n        let args = vec![ OsStr::new(\"--version\"), OsStr::new(\"me\") ];\n        let opts = Options::parse(args, &None);\n        assert!(matches!(opts, OptionsResult::Version(_)));\n    }\n}\n"
  },
  {
    "path": "src/options/view.rs",
    "content": "use crate::fs::feature::xattr;\nuse crate::options::{flags, OptionsError, NumberSource, Vars};\nuse crate::options::parser::MatchedFlags;\nuse crate::output::{View, Mode, TerminalWidth, grid, details};\nuse crate::output::grid_details::{self, RowThreshold};\nuse crate::output::file_name::Options as FileStyle;\nuse crate::output::table::{TimeTypes, SizeFormat, UserFormat, Columns, Options as TableOptions};\nuse crate::output::time::TimeFormat;\n\n\nimpl View {\n    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        let mode = Mode::deduce(matches, vars)?;\n        let width = TerminalWidth::deduce(vars)?;\n        let file_style = FileStyle::deduce(matches, vars)?;\n        Ok(Self { mode, width, file_style })\n    }\n}\n\n\nimpl Mode {\n\n    /// Determine which viewing mode to use based on the user’s options.\n    ///\n    /// As with the other options, arguments are scanned right-to-left and the\n    /// first flag found is matched, so `exa --oneline --long` will pick a\n    /// details view, and `exa --long --oneline` will pick the lines view.\n    ///\n    /// This is complicated a little by the fact that `--grid` and `--tree`\n    /// can also combine with `--long`, so care has to be taken to use the\n    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        let flag = matches.has_where_any(|f| f.matches(&flags::LONG) || f.matches(&flags::ONE_LINE)\n                                          || f.matches(&flags::GRID) || f.matches(&flags::TREE));\n\n        let flag = if let Some(f) = flag { f } else {\n            Self::strict_check_long_flags(matches)?;\n            let grid = grid::Options::deduce(matches)?;\n            return Ok(Self::Grid(grid));\n        };\n\n        if flag.matches(&flags::LONG)\n        || (flag.matches(&flags::TREE) && matches.has(&flags::LONG)?)\n        || (flag.matches(&flags::GRID) && matches.has(&flags::LONG)?)\n        {\n            let _ = matches.has(&flags::LONG)?;\n            let details = details::Options::deduce_long(matches, vars)?;\n\n            let flag = matches.has_where_any(|f| f.matches(&flags::GRID) || f.matches(&flags::TREE));\n\n            if flag.is_some() && flag.unwrap().matches(&flags::GRID) {\n                let _ = matches.has(&flags::GRID)?;\n                let grid = grid::Options::deduce(matches)?;\n                let row_threshold = RowThreshold::deduce(vars)?;\n                let grid_details = grid_details::Options { grid, details, row_threshold };\n                return Ok(Self::GridDetails(grid_details));\n            }\n\n            // the --tree case is handled by the DirAction parser later\n            return Ok(Self::Details(details));\n        }\n\n        Self::strict_check_long_flags(matches)?;\n\n        if flag.matches(&flags::TREE) {\n            let _ = matches.has(&flags::TREE)?;\n            let details = details::Options::deduce_tree(matches)?;\n            return Ok(Self::Details(details));\n        }\n\n        if flag.matches(&flags::ONE_LINE) {\n            let _ = matches.has(&flags::ONE_LINE)?;\n            return Ok(Self::Lines);\n        }\n\n        let grid = grid::Options::deduce(matches)?;\n        Ok(Self::Grid(grid))\n    }\n\n    fn strict_check_long_flags(matches: &MatchedFlags<'_>) -> Result<(), OptionsError> {\n        // If --long hasn’t been passed, then check if we need to warn the\n        // user about flags that won’t have any effect.\n        if matches.is_strict() {\n            for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,\n                             &flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP, &flags::NUMERIC ] {\n                if matches.has(option)? {\n                    return Err(OptionsError::Useless(*option, false, &flags::LONG));\n                }\n            }\n\n            if matches.has(&flags::GIT)? {\n                return Err(OptionsError::Useless(&flags::GIT, false, &flags::LONG));\n            }\n            else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? {\n                return Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));\n            }\n        }\n\n        Ok(())\n    }\n}\n\n\nimpl grid::Options {\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let grid = grid::Options {\n            across: matches.has(&flags::ACROSS)?,\n        };\n\n        Ok(grid)\n    }\n}\n\n\nimpl details::Options {\n    fn deduce_tree(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let details = details::Options {\n            table: None,\n            header: false,\n            xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,\n        };\n\n        Ok(details)\n    }\n\n    fn deduce_long<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        if matches.is_strict() {\n            if matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? {\n                return Err(OptionsError::Useless(&flags::ACROSS, true, &flags::LONG));\n            }\n            else if matches.has(&flags::ONE_LINE)? {\n                return Err(OptionsError::Useless(&flags::ONE_LINE, true, &flags::LONG));\n            }\n        }\n\n        Ok(details::Options {\n            table: Some(TableOptions::deduce(matches, vars)?),\n            header: matches.has(&flags::HEADER)?,\n            xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,\n        })\n    }\n}\n\n\nimpl TerminalWidth {\n    fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {\n        use crate::options::vars;\n\n        if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) {\n            match columns.parse() {\n                Ok(width) => {\n                    Ok(Self::Set(width))\n                }\n                Err(e) => {\n                    let source = NumberSource::Env(vars::COLUMNS);\n                    Err(OptionsError::FailedParse(columns, source, e))\n                }\n            }\n        }\n        else {\n            Ok(Self::Automatic)\n        }\n    }\n}\n\n\nimpl RowThreshold {\n    fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {\n        use crate::options::vars;\n\n        if let Some(columns) = vars.get(vars::EXA_GRID_ROWS).and_then(|s| s.into_string().ok()) {\n            match columns.parse() {\n                Ok(rows) => {\n                    Ok(Self::MinimumRows(rows))\n                }\n                Err(e) => {\n                    let source = NumberSource::Env(vars::EXA_GRID_ROWS);\n                    Err(OptionsError::FailedParse(columns, source, e))\n                }\n            }\n        }\n        else {\n            Ok(Self::AlwaysGrid)\n        }\n    }\n}\n\n\nimpl TableOptions {\n    fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        let time_format = TimeFormat::deduce(matches, vars)?;\n        let size_format = SizeFormat::deduce(matches)?;\n        let user_format = UserFormat::deduce(matches)?;\n        let columns = Columns::deduce(matches)?;\n        Ok(Self { size_format, time_format, user_format, columns })\n    }\n}\n\n\nimpl Columns {\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let time_types = TimeTypes::deduce(matches)?;\n        let git = matches.has(&flags::GIT)?;\n\n        let blocks = matches.has(&flags::BLOCKS)?;\n        let group  = matches.has(&flags::GROUP)?;\n        let inode  = matches.has(&flags::INODE)?;\n        let links  = matches.has(&flags::LINKS)?;\n        let octal  = matches.has(&flags::OCTAL)?;\n\n        let permissions = ! matches.has(&flags::NO_PERMISSIONS)?;\n        let filesize =    ! matches.has(&flags::NO_FILESIZE)?;\n        let user =        ! matches.has(&flags::NO_USER)?;\n\n        Ok(Self { time_types, inode, links, blocks, group, git, octal, permissions, filesize, user })\n    }\n}\n\n\nimpl SizeFormat {\n\n    /// Determine which file size to use in the file size column based on\n    /// the user’s options.\n    ///\n    /// The default mode is to use the decimal prefixes, as they are the\n    /// most commonly-understood, and don’t involve trying to parse large\n    /// strings of digits in your head. Changing the format to anything else\n    /// involves the `--binary` or `--bytes` flags, and these conflict with\n    /// each other.\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?;\n\n        Ok(match flag {\n            Some(f) if f.matches(&flags::BINARY)  => Self::BinaryBytes,\n            Some(f) if f.matches(&flags::BYTES)   => Self::JustBytes,\n            _                                     => Self::DecimalBytes,\n        })\n    }\n}\n\n\nimpl TimeFormat {\n\n    /// Determine how time should be formatted in timestamp columns.\n    fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {\n        let word =\n            if let Some(w) = matches.get(&flags::TIME_STYLE)? {\n                w.to_os_string()\n            }\n            else {\n                use crate::options::vars;\n                match vars.get(vars::TIME_STYLE) {\n                    Some(ref t) if ! t.is_empty()  => t.clone(),\n                    _                              => return Ok(Self::DefaultFormat)\n                }\n            };\n\n        if &word == \"default\" {\n            Ok(Self::DefaultFormat)\n        }\n        else if &word == \"iso\" {\n            Ok(Self::ISOFormat)\n        }\n        else if &word == \"long-iso\" {\n            Ok(Self::LongISO)\n        }\n        else if &word == \"full-iso\" {\n            Ok(Self::FullISO)\n        }\n        else {\n            Err(OptionsError::BadArgument(&flags::TIME_STYLE, word))\n        }\n    }\n}\n\n\nimpl UserFormat {\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let flag = matches.has(&flags::NUMERIC)?;\n        Ok(if flag { Self::Numeric } else { Self::Name })\n    }\n}\n\n\nimpl TimeTypes {\n\n    /// Determine which of a file’s time fields should be displayed for it\n    /// based on the user’s options.\n    ///\n    /// There are two separate ways to pick which fields to show: with a\n    /// flag (such as `--modified`) or with a parameter (such as\n    /// `--time=modified`). An error is signaled if both ways are used.\n    ///\n    /// It’s valid to show more than one column by passing in more than one\n    /// option, but passing *no* options means that the user just wants to\n    /// see the default set.\n    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {\n        let possible_word = matches.get(&flags::TIME)?;\n        let modified = matches.has(&flags::MODIFIED)?;\n        let changed  = matches.has(&flags::CHANGED)?;\n        let accessed = matches.has(&flags::ACCESSED)?;\n        let created  = matches.has(&flags::CREATED)?;\n\n        let no_time = matches.has(&flags::NO_TIME)?;\n\n        let time_types = if no_time {\n            Self { modified: false, changed: false, accessed: false, created: false }\n        } else if let Some(word) = possible_word {\n            if modified {\n                return Err(OptionsError::Useless(&flags::MODIFIED, true, &flags::TIME));\n            }\n            else if changed {\n                return Err(OptionsError::Useless(&flags::CHANGED, true, &flags::TIME));\n            }\n            else if accessed {\n                return Err(OptionsError::Useless(&flags::ACCESSED, true, &flags::TIME));\n            }\n            else if created {\n                return Err(OptionsError::Useless(&flags::CREATED, true, &flags::TIME));\n            }\n            else if word == \"mod\" || word == \"modified\" {\n                Self { modified: true,  changed: false, accessed: false, created: false }\n            }\n            else if word == \"ch\" || word == \"changed\" {\n                Self { modified: false, changed: true,  accessed: false, created: false }\n            }\n            else if word == \"acc\" || word == \"accessed\" {\n                Self { modified: false, changed: false, accessed: true,  created: false }\n            }\n            else if word == \"cr\" || word == \"created\" {\n                Self { modified: false, changed: false, accessed: false, created: true  }\n            }\n            else {\n                return Err(OptionsError::BadArgument(&flags::TIME, word.into()));\n            }\n        }\n        else if modified || changed || accessed || created {\n            Self { modified, changed, accessed, created }\n        }\n        else {\n            Self::default()\n        };\n\n        Ok(time_types)\n    }\n}\n\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use std::ffi::OsString;\n    use crate::options::flags;\n    use crate::options::parser::{Flag, Arg};\n\n    use crate::options::test::parse_for_test;\n    use crate::options::test::Strictnesses::*;\n\n    static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES,    &flags::TIME_STYLE,\n                                   &flags::TIME,   &flags::MODIFIED, &flags::CHANGED,\n                                   &flags::CREATED, &flags::ACCESSED,\n                                   &flags::HEADER, &flags::GROUP,  &flags::INODE, &flags::GIT,\n                                   &flags::LINKS,  &flags::BLOCKS, &flags::LONG,  &flags::LEVEL,\n                                   &flags::GRID,   &flags::ACROSS, &flags::ONE_LINE, &flags::TREE,\n                                   &flags::NUMERIC ];\n\n    macro_rules! test {\n\n        ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {\n            /// Macro that writes a test.\n            /// If testing both strictnesses, they’ll both be done in the same function.\n            #[test]\n            fn $name() {\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {\n                    assert_eq!(result, $result);\n                }\n            }\n        };\n\n        ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => {\n            /// Special macro for testing Err results.\n            /// This is needed because sometimes the Ok type doesn’t implement `PartialEq`.\n            #[test]\n            fn $name() {\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {\n                    assert_eq!(result.unwrap_err(), $result);\n                }\n            }\n        };\n\n        ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => {\n            /// More general macro for testing against a pattern.\n            /// Instead of using `PartialEq`, this just tests if it matches a pat.\n            #[test]\n            fn $name() {\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {\n                    println!(\"Testing {:?}\", result);\n                    match result {\n                        $pat => assert!(true),\n                        _    => assert!(false),\n                    }\n                }\n            }\n        };\n\n\n        ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => err $result:expr) => {\n            /// Like above, but with $vars.\n            #[test]\n            fn $name() {\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {\n                    assert_eq!(result.unwrap_err(), $result);\n                }\n            }\n        };\n\n        ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => like $pat:pat) => {\n            /// Like further above, but with $vars.\n            #[test]\n            fn $name() {\n                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {\n                    println!(\"Testing {:?}\", result);\n                    match result {\n                        $pat => assert!(true),\n                        _    => assert!(false),\n                    }\n                }\n            }\n        };\n    }\n\n\n    mod size_formats {\n        use super::*;\n\n        // Default behaviour\n        test!(empty:   SizeFormat <- [];                       Both => Ok(SizeFormat::DecimalBytes));\n\n        // Individual flags\n        test!(binary:  SizeFormat <- [\"--binary\"];             Both => Ok(SizeFormat::BinaryBytes));\n        test!(bytes:   SizeFormat <- [\"--bytes\"];              Both => Ok(SizeFormat::JustBytes));\n\n        // Overriding\n        test!(both_1:  SizeFormat <- [\"--binary\", \"--binary\"];  Last => Ok(SizeFormat::BinaryBytes));\n        test!(both_2:  SizeFormat <- [\"--bytes\",  \"--binary\"];  Last => Ok(SizeFormat::BinaryBytes));\n        test!(both_3:  SizeFormat <- [\"--binary\", \"--bytes\"];   Last => Ok(SizeFormat::JustBytes));\n        test!(both_4:  SizeFormat <- [\"--bytes\",  \"--bytes\"];   Last => Ok(SizeFormat::JustBytes));\n\n        test!(both_5:  SizeFormat <- [\"--binary\", \"--binary\"];  Complain => err OptionsError::Duplicate(Flag::Long(\"binary\"), Flag::Long(\"binary\")));\n        test!(both_6:  SizeFormat <- [\"--bytes\",  \"--binary\"];  Complain => err OptionsError::Duplicate(Flag::Long(\"bytes\"),  Flag::Long(\"binary\")));\n        test!(both_7:  SizeFormat <- [\"--binary\", \"--bytes\"];   Complain => err OptionsError::Duplicate(Flag::Long(\"binary\"), Flag::Long(\"bytes\")));\n        test!(both_8:  SizeFormat <- [\"--bytes\",  \"--bytes\"];   Complain => err OptionsError::Duplicate(Flag::Long(\"bytes\"),  Flag::Long(\"bytes\")));\n    }\n\n\n    mod time_formats {\n        use super::*;\n\n        // These tests use pattern matching because TimeFormat doesn’t\n        // implement PartialEq.\n\n        // Default behaviour\n        test!(empty:     TimeFormat <- [], None;                            Both => like Ok(TimeFormat::DefaultFormat));\n\n        // Individual settings\n        test!(default:   TimeFormat <- [\"--time-style=default\"], None;      Both => like Ok(TimeFormat::DefaultFormat));\n        test!(iso:       TimeFormat <- [\"--time-style\", \"iso\"], None;       Both => like Ok(TimeFormat::ISOFormat));\n        test!(long_iso:  TimeFormat <- [\"--time-style=long-iso\"], None;     Both => like Ok(TimeFormat::LongISO));\n        test!(full_iso:  TimeFormat <- [\"--time-style\", \"full-iso\"], None;  Both => like Ok(TimeFormat::FullISO));\n\n        // Overriding\n        test!(actually:  TimeFormat <- [\"--time-style=default\", \"--time-style\", \"iso\"], None;  Last => like Ok(TimeFormat::ISOFormat));\n        test!(actual_2:  TimeFormat <- [\"--time-style=default\", \"--time-style\", \"iso\"], None;  Complain => err OptionsError::Duplicate(Flag::Long(\"time-style\"), Flag::Long(\"time-style\")));\n\n        test!(nevermind: TimeFormat <- [\"--time-style\", \"long-iso\", \"--time-style=full-iso\"], None;  Last => like Ok(TimeFormat::FullISO));\n        test!(nevermore: TimeFormat <- [\"--time-style\", \"long-iso\", \"--time-style=full-iso\"], None;  Complain => err OptionsError::Duplicate(Flag::Long(\"time-style\"), Flag::Long(\"time-style\")));\n\n        // Errors\n        test!(daily:     TimeFormat <- [\"--time-style=24-hour\"], None;  Both => err OptionsError::BadArgument(&flags::TIME_STYLE, OsString::from(\"24-hour\")));\n\n        // `TIME_STYLE` environment variable is defined.\n        // If the time-style argument is not given, `TIME_STYLE` is used.\n        test!(use_env:     TimeFormat <- [], Some(\"long-iso\".into());  Both => like Ok(TimeFormat::LongISO));\n\n        // If the time-style argument is given, `TIME_STYLE` is overriding.\n        test!(override_env:     TimeFormat <- [\"--time-style=full-iso\"], Some(\"long-iso\".into());  Both => like Ok(TimeFormat::FullISO));\n    }\n\n\n    mod time_types {\n        use super::*;\n\n        // Default behaviour\n        test!(empty:     TimeTypes <- [];                      Both => Ok(TimeTypes::default()));\n\n        // Modified\n        test!(modified:  TimeTypes <- [\"--modified\"];          Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));\n        test!(m:         TimeTypes <- [\"-m\"];                  Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));\n        test!(time_mod:  TimeTypes <- [\"--time=modified\"];     Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));\n        test!(t_m:       TimeTypes <- [\"-tmod\"];               Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));\n\n        // Changed\n        #[cfg(target_family = \"unix\")]\n        test!(changed:   TimeTypes <- [\"--changed\"];           Both => Ok(TimeTypes { modified: false, changed: true,  accessed: false, created: false }));\n        #[cfg(target_family = \"unix\")]\n        test!(time_ch:   TimeTypes <- [\"--time=changed\"];      Both => Ok(TimeTypes { modified: false, changed: true,  accessed: false, created: false }));\n        #[cfg(target_family = \"unix\")]\n        test!(t_ch:    TimeTypes <- [\"-t\", \"ch\"];              Both => Ok(TimeTypes { modified: false, changed: true,  accessed: false, created: false }));\n\n        // Accessed\n        test!(acc:       TimeTypes <- [\"--accessed\"];          Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));\n        test!(a:         TimeTypes <- [\"-u\"];                  Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));\n        test!(time_acc:  TimeTypes <- [\"--time\", \"accessed\"];  Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));\n        test!(time_a:    TimeTypes <- [\"-t\", \"acc\"];           Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));\n\n        // Created\n        test!(cr:        TimeTypes <- [\"--created\"];           Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));\n        test!(c:         TimeTypes <- [\"-U\"];                  Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));\n        test!(time_cr:   TimeTypes <- [\"--time=created\"];      Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));\n        test!(t_cr:      TimeTypes <- [\"-tcr\"];                Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));\n\n        // Multiples\n        test!(time_uu:   TimeTypes <- [\"-u\", \"--modified\"];    Both => Ok(TimeTypes { modified: true,  changed: false, accessed: true,  created: false }));\n\n\n        // Errors\n        test!(time_tea:  TimeTypes <- [\"--time=tea\"];          Both => err OptionsError::BadArgument(&flags::TIME, OsString::from(\"tea\")));\n        test!(t_ea:      TimeTypes <- [\"-tea\"];                Both => err OptionsError::BadArgument(&flags::TIME, OsString::from(\"ea\")));\n\n        // Overriding\n        test!(overridden:   TimeTypes <- [\"-tcr\", \"-tmod\"];    Last => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));\n        test!(overridden_2: TimeTypes <- [\"-tcr\", \"-tmod\"];    Complain => err OptionsError::Duplicate(Flag::Short(b't'), Flag::Short(b't')));\n    }\n\n\n    mod views {\n        use super::*;\n\n        use crate::output::grid::Options as GridOptions;\n\n\n        // Default\n        test!(empty:         Mode <- [], None;            Both => like Ok(Mode::Grid(_)));\n\n        // Grid views\n        test!(original_g:    Mode <- [\"-G\"], None;        Both => like Ok(Mode::Grid(GridOptions { across: false, .. })));\n        test!(grid:          Mode <- [\"--grid\"], None;    Both => like Ok(Mode::Grid(GridOptions { across: false, .. })));\n        test!(across:        Mode <- [\"--across\"], None;  Both => like Ok(Mode::Grid(GridOptions { across: true,  .. })));\n        test!(gracross:      Mode <- [\"-xG\"], None;       Both => like Ok(Mode::Grid(GridOptions { across: true,  .. })));\n\n        // Lines views\n        test!(lines:         Mode <- [\"--oneline\"], None;     Both => like Ok(Mode::Lines));\n        test!(prima:         Mode <- [\"-1\"], None;            Both => like Ok(Mode::Lines));\n\n        // Details views\n        test!(long:          Mode <- [\"--long\"], None;    Both => like Ok(Mode::Details(_)));\n        test!(ell:           Mode <- [\"-l\"], None;        Both => like Ok(Mode::Details(_)));\n\n        // Grid-details views\n        test!(lid:           Mode <- [\"--long\", \"--grid\"], None;  Both => like Ok(Mode::GridDetails(_)));\n        test!(leg:           Mode <- [\"-lG\"], None;               Both => like Ok(Mode::GridDetails(_)));\n\n        // Options that do nothing with --long\n        test!(long_across:   Mode <- [\"--long\", \"--across\"],   None;  Last => like Ok(Mode::Details(_)));\n\n        // Options that do nothing without --long\n        test!(just_header:   Mode <- [\"--header\"],   None;  Last => like Ok(Mode::Grid(_)));\n        test!(just_group:    Mode <- [\"--group\"],    None;  Last => like Ok(Mode::Grid(_)));\n        test!(just_inode:    Mode <- [\"--inode\"],    None;  Last => like Ok(Mode::Grid(_)));\n        test!(just_links:    Mode <- [\"--links\"],    None;  Last => like Ok(Mode::Grid(_)));\n        test!(just_blocks:   Mode <- [\"--blocks\"],   None;  Last => like Ok(Mode::Grid(_)));\n        test!(just_binary:   Mode <- [\"--binary\"],   None;  Last => like Ok(Mode::Grid(_)));\n        test!(just_bytes:    Mode <- [\"--bytes\"],    None;  Last => like Ok(Mode::Grid(_)));\n        test!(just_numeric:  Mode <- [\"--numeric\"],  None;  Last => like Ok(Mode::Grid(_)));\n\n        #[cfg(feature = \"git\")]\n        test!(just_git:      Mode <- [\"--git\"],    None;  Last => like Ok(Mode::Grid(_)));\n\n        test!(just_header_2: Mode <- [\"--header\"],   None;  Complain => err OptionsError::Useless(&flags::HEADER,  false, &flags::LONG));\n        test!(just_group_2:  Mode <- [\"--group\"],    None;  Complain => err OptionsError::Useless(&flags::GROUP,   false, &flags::LONG));\n        test!(just_inode_2:  Mode <- [\"--inode\"],    None;  Complain => err OptionsError::Useless(&flags::INODE,   false, &flags::LONG));\n        test!(just_links_2:  Mode <- [\"--links\"],    None;  Complain => err OptionsError::Useless(&flags::LINKS,   false, &flags::LONG));\n        test!(just_blocks_2: Mode <- [\"--blocks\"],   None;  Complain => err OptionsError::Useless(&flags::BLOCKS,  false, &flags::LONG));\n        test!(just_binary_2: Mode <- [\"--binary\"],   None;  Complain => err OptionsError::Useless(&flags::BINARY,  false, &flags::LONG));\n        test!(just_bytes_2:  Mode <- [\"--bytes\"],    None;  Complain => err OptionsError::Useless(&flags::BYTES,   false, &flags::LONG));\n        test!(just_numeric2: Mode <- [\"--numeric\"],  None;  Complain => err OptionsError::Useless(&flags::NUMERIC, false, &flags::LONG));\n\n        #[cfg(feature = \"git\")]\n        test!(just_git_2:    Mode <- [\"--git\"],    None;  Complain => err OptionsError::Useless(&flags::GIT,    false, &flags::LONG));\n\n        // Contradictions and combinations\n        test!(lgo:           Mode <- [\"--long\", \"--grid\", \"--oneline\"], None;  Both => like Ok(Mode::Lines));\n        test!(lgt:           Mode <- [\"--long\", \"--grid\", \"--tree\"],    None;  Both => like Ok(Mode::Details(_)));\n        test!(tgl:           Mode <- [\"--tree\", \"--grid\", \"--long\"],    None;  Both => like Ok(Mode::GridDetails(_)));\n        test!(tlg:           Mode <- [\"--tree\", \"--long\", \"--grid\"],    None;  Both => like Ok(Mode::GridDetails(_)));\n        test!(ot:            Mode <- [\"--oneline\", \"--tree\"],           None;  Both => like Ok(Mode::Details(_)));\n        test!(og:            Mode <- [\"--oneline\", \"--grid\"],           None;  Both => like Ok(Mode::Grid(_)));\n        test!(tg:            Mode <- [\"--tree\", \"--grid\"],              None;  Both => like Ok(Mode::Grid(_)));\n    }\n}\n"
  },
  {
    "path": "src/output/cell.rs",
    "content": "//! The `TextCell` type for the details and lines views.\n\nuse std::iter::Sum;\nuse std::ops::{Add, Deref, DerefMut};\n\nuse ansi_term::{Style, ANSIString, ANSIStrings};\nuse unicode_width::UnicodeWidthStr;\n\n\n/// An individual cell that holds text in a table, used in the details and\n/// lines views to store ANSI-terminal-formatted data before it is printed.\n///\n/// A text cell is made up of zero or more strings coupled with the\n/// pre-computed length of all the strings combined. When constructing details\n/// or grid-details tables, the length will have to be queried multiple times,\n/// so it makes sense to cache it.\n///\n/// (This used to be called `Cell`, but was renamed because there’s a Rust\n/// type by that name too.)\n#[derive(PartialEq, Debug, Clone, Default)]\npub struct TextCell {\n\n    /// The contents of this cell, as a vector of ANSI-styled strings.\n    pub contents: TextCellContents,\n\n    /// The Unicode “display width” of this cell.\n    pub width: DisplayWidth,\n}\n\nimpl Deref for TextCell {\n    type Target = TextCellContents;\n\n    fn deref(&self) -> &Self::Target {\n        &self.contents\n    }\n}\n\nimpl TextCell {\n\n    /// Creates a new text cell that holds the given text in the given style,\n    /// computing the Unicode width of the text.\n    pub fn paint(style: Style, text: String) -> Self {\n        let width = DisplayWidth::from(&*text);\n\n        Self {\n            contents: vec![ style.paint(text) ].into(),\n            width,\n        }\n    }\n\n    /// Creates a new text cell that holds the given text in the given style,\n    /// computing the Unicode width of the text. (This could be merged with\n    /// `paint`, but.)\n    pub fn paint_str(style: Style, text: &'static str) -> Self {\n        let width = DisplayWidth::from(text);\n\n        Self {\n            contents: vec![ style.paint(text) ].into(),\n            width,\n        }\n    }\n\n    /// Creates a new “blank” text cell that contains a single hyphen in the\n    /// given style, which should be the “punctuation” style from a `Colours`\n    /// value.\n    ///\n    /// This is used in place of empty table cells, as it is easier to read\n    /// tabular data when there is *something* in each cell.\n    pub fn blank(style: Style) -> Self {\n        Self {\n            contents: vec![ style.paint(\"-\") ].into(),\n            width:    DisplayWidth::from(1),\n        }\n    }\n\n    /// Adds the given number of unstyled spaces after this cell.\n    ///\n    /// This method allocates a `String` to hold the spaces.\n    pub fn add_spaces(&mut self, count: usize) {\n        (*self.width) += count;\n\n        let spaces: String = \" \".repeat(count);\n        self.contents.0.push(Style::default().paint(spaces));\n    }\n\n    /// Adds the contents of another `ANSIString` to the end of this cell.\n    pub fn push(&mut self, string: ANSIString<'static>, extra_width: usize) {\n        self.contents.0.push(string);\n        (*self.width) += extra_width;\n    }\n\n    /// Adds all the contents of another `TextCell` to the end of this cell.\n    pub fn append(&mut self, other: Self) {\n        (*self.width) += *other.width;\n        self.contents.0.extend(other.contents.0);\n    }\n}\n\n\n// I’d like to eventually abstract cells so that instead of *every* cell\n// storing a vector, only variable-length cells would, and individual cells\n// would just store an array of a fixed length (which would usually be just 1\n// or 2), which wouldn’t require a heap allocation.\n//\n// For examples, look at the `render_*` methods in the `Table` object in the\n// details view:\n//\n// - `render_blocks`, `inode`, and `links` will always return a\n//   one-string-long TextCell;\n// - `render_size` will return one or two strings in a TextCell, depending on\n//   the size and whether one is present;\n// - `render_permissions` will return ten or eleven strings;\n// - `filename` and `symlink_filename` in the output module root return six or\n//   five strings.\n//\n// In none of these cases are we dealing with a *truly variable* number of\n// strings: it is only when the strings are concatenated together do we need a\n// growable, heap-allocated buffer.\n//\n// So it would be nice to abstract the `TextCell` type so instead of a `Vec`,\n// it can use anything of type `T: IntoIterator<Item=ANSIString<’static>>`.\n// This would allow us to still hold all the data, but allocate less.\n//\n// But exa still has bugs and I need to fix those first :(\n\n\n/// The contents of a text cell, as a vector of ANSI-styled strings.\n///\n/// It’s possible to use this type directly in the case where you want a\n/// `TextCell` but aren’t concerned with tracking its width, because it occurs\n/// in the final cell of a table or grid and there’s no point padding it. This\n/// happens when dealing with file names.\n#[derive(PartialEq, Debug, Clone, Default)]\npub struct TextCellContents(Vec<ANSIString<'static>>);\n\nimpl From<Vec<ANSIString<'static>>> for TextCellContents {\n    fn from(strings: Vec<ANSIString<'static>>) -> Self {\n        Self(strings)\n    }\n}\n\nimpl Deref for TextCellContents {\n    type Target = [ANSIString<'static>];\n\n    fn deref(&self) -> &Self::Target {\n        &*self.0\n    }\n}\n\n// No DerefMut implementation here — it would be publicly accessible, and as\n// the contents only get changed in this module, the mutators in the struct\n// above can just access the value directly.\n\nimpl TextCellContents {\n\n    /// Produces an `ANSIStrings` value that can be used to print the styled\n    /// values of this cell as an ANSI-terminal-formatted string.\n    pub fn strings(&self) -> ANSIStrings<'_> {\n        ANSIStrings(&self.0)\n    }\n\n    /// Calculates the width that a cell with these contents would take up, by\n    /// counting the number of characters in each unformatted ANSI string.\n    pub fn width(&self) -> DisplayWidth {\n        self.0.iter()\n            .map(|anstr| DisplayWidth::from(&**anstr))\n            .sum()\n    }\n\n    /// Promotes these contents to a full cell containing them alongside\n    /// their calculated width.\n    pub fn promote(self) -> TextCell {\n        TextCell {\n            width: self.width(),\n            contents: self,\n        }\n    }\n}\n\n\n/// The Unicode “display width” of a string.\n///\n/// This is related to the number of *graphemes* of a string, rather than the\n/// number of *characters*, or *bytes*: although most characters are one\n/// column wide, a few can be two columns wide, and this is important to note\n/// when calculating widths for displaying tables in a terminal.\n///\n/// This type is used to ensure that the width, rather than the length, is\n/// used when constructing a `TextCell` — it’s too easy to write something\n/// like `file_name.len()` and assume it will work!\n///\n/// It has `From` impls that convert an input string or fixed with to values\n/// of this type, and will `Deref` to the contained `usize` value.\n#[derive(PartialEq, Eq, Debug, Clone, Copy, Default)]\npub struct DisplayWidth(usize);\n\nimpl<'a> From<&'a str> for DisplayWidth {\n    fn from(input: &'a str) -> Self {\n        Self(UnicodeWidthStr::width(input))\n    }\n}\n\nimpl From<usize> for DisplayWidth {\n    fn from(width: usize) -> Self {\n        Self(width)\n    }\n}\n\nimpl Deref for DisplayWidth {\n    type Target = usize;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for DisplayWidth {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\nimpl Add for DisplayWidth {\n    type Output = Self;\n\n    fn add(self, rhs: Self) -> Self::Output {\n        Self(self.0 + rhs.0)\n    }\n}\n\nimpl Add<usize> for DisplayWidth {\n    type Output = Self;\n\n    fn add(self, rhs: usize) -> Self::Output {\n        Self(self.0 + rhs)\n    }\n}\n\nimpl Sum for DisplayWidth {\n    fn sum<I>(iter: I) -> Self\n    where I: Iterator<Item = Self>\n    {\n        iter.fold(Self(0), Add::add)\n    }\n}\n\n\n#[cfg(test)]\nmod width_unit_test {\n    use super::DisplayWidth;\n\n    #[test]\n    fn empty_string() {\n        let cell = DisplayWidth::from(\"\");\n        assert_eq!(*cell, 0);\n    }\n\n    #[test]\n    fn test_string() {\n        let cell = DisplayWidth::from(\"Diss Playwidth\");\n        assert_eq!(*cell, 14);\n    }\n\n    #[test]\n    fn addition() {\n        let cell_one = DisplayWidth::from(\"/usr/bin/\");\n        let cell_two = DisplayWidth::from(\"drinking\");\n        assert_eq!(*(cell_one + cell_two), 17);\n    }\n\n    #[test]\n    fn addition_usize() {\n        let cell = DisplayWidth::from(\"/usr/bin/\");\n        assert_eq!(*(cell + 8), 17);\n    }\n}\n"
  },
  {
    "path": "src/output/details.rs",
    "content": "//! The **Details** output view displays each file as a row in a table.\n//!\n//! It’s used in the following situations:\n//!\n//! - Most commonly, when using the `--long` command-line argument to display the\n//!   details of each file, which requires using a table view to hold all the data;\n//! - When using the `--tree` argument, which uses the same table view to display\n//!   each file on its own line, with the table providing the tree characters;\n//! - When using both the `--long` and `--grid` arguments, which constructs a\n//!   series of tables to fit all the data on the screen.\n//!\n//! You will probably recognise it from the `ls --long` command. It looks like\n//! this:\n//!\n//! ```text\n//!     .rw-r--r--  9.6k ben 29 Jun 16:16 Cargo.lock\n//!     .rw-r--r--   547 ben 23 Jun 10:54 Cargo.toml\n//!     .rw-r--r--  1.1k ben 23 Nov  2014 LICENCE\n//!     .rw-r--r--  2.5k ben 21 May 14:38 README.md\n//!     .rw-r--r--  382k ben  8 Jun 21:00 screenshot.png\n//!     drwxr-xr-x     - ben 29 Jun 14:50 src\n//!     drwxr-xr-x     - ben 28 Jun 19:53 target\n//! ```\n//!\n//! The table is constructed by creating a `Table` value, which produces a `Row`\n//! value for each file. These rows can contain a vector of `Cell`s, or they can\n//! contain depth information for the tree view, or both. These are described\n//! below.\n//!\n//!\n//! ## Constructing Detail Views\n//!\n//! When using the `--long` command-line argument, the details of each file are\n//! displayed next to its name.\n//!\n//! The table holds a vector of all the column types. For each file and column, a\n//! `Cell` value containing the ANSI-coloured text and Unicode width of each cell\n//! is generated, with the row and column determined by indexing into both arrays.\n//!\n//! The column types vector does not actually include the filename. This is\n//! because the filename is always the rightmost field, and as such, it does not\n//! need to have its width queried or be padded with spaces.\n//!\n//! To illustrate the above:\n//!\n//! ```text\n//!     ┌─────────────────────────────────────────────────────────────────────────┐\n//!     │ columns: [ Permissions,  Size,   User,  Date(Modified) ]                │\n//!     ├─────────────────────────────────────────────────────────────────────────┤\n//!     │   rows:  cells:                                            filename:    │\n//!     │   row 1: [ \".rw-r--r--\", \"9.6k\", \"ben\", \"29 Jun 16:16\" ]   Cargo.lock   │\n//!     │   row 2: [ \".rw-r--r--\",  \"547\", \"ben\", \"23 Jun 10:54\" ]   Cargo.toml   │\n//!     │   row 3: [ \"drwxr-xr-x\",    \"-\", \"ben\", \"29 Jun 14:50\" ]   src          │\n//!     │   row 4: [ \"drwxr-xr-x\",    \"-\", \"ben\", \"28 Jun 19:53\" ]   target       │\n//!     └─────────────────────────────────────────────────────────────────────────┘\n//! ```\n//!\n//! Each column in the table needs to be resized to fit its widest argument. This\n//! means that we must wait until every row has been added to the table before it\n//! can be displayed, in order to make sure that every column is wide enough.\n\n\nuse std::io::{self, Write};\nuse std::mem::MaybeUninit;\nuse std::path::PathBuf;\nuse std::vec::IntoIter as VecIntoIter;\n\nuse ansi_term::Style;\nuse scoped_threadpool::Pool;\n\nuse crate::fs::{Dir, File};\nuse crate::fs::dir_action::RecurseOptions;\nuse crate::fs::feature::git::GitCache;\nuse crate::fs::feature::xattr::{Attribute, FileAttributes};\nuse crate::fs::filter::FileFilter;\nuse crate::output::cell::TextCell;\nuse crate::output::file_name::Options as FileStyle;\nuse crate::output::table::{Table, Options as TableOptions, Row as TableRow};\nuse crate::output::tree::{TreeTrunk, TreeParams, TreeDepth};\nuse crate::theme::Theme;\n\n\n/// With the **Details** view, the output gets formatted into columns, with\n/// each `Column` object showing some piece of information about the file,\n/// such as its size, or its permissions.\n///\n/// To do this, the results have to be written to a table, instead of\n/// displaying each file immediately. Then, the width of each column can be\n/// calculated based on the individual results, and the fields are padded\n/// during output.\n///\n/// Almost all the heavy lifting is done in a Table object, which handles the\n/// columns for each row.\n#[derive(PartialEq, Eq, Debug)]\npub struct Options {\n\n    /// Options specific to drawing a table.\n    ///\n    /// Directories themselves can pick which columns are *added* to this\n    /// list, such as the Git column.\n    pub table: Option<TableOptions>,\n\n    /// Whether to show a header line or not.\n    pub header: bool,\n\n    /// Whether to show each file’s extended attributes.\n    pub xattr: bool,\n}\n\n\npub struct Render<'a> {\n    pub dir: Option<&'a Dir>,\n    pub files: Vec<File<'a>>,\n    pub theme: &'a Theme,\n    pub file_style: &'a FileStyle,\n    pub opts: &'a Options,\n\n    /// Whether to recurse through directories with a tree view, and if so,\n    /// which options to use. This field is only relevant here if the `tree`\n    /// field of the RecurseOptions is `true`.\n    pub recurse: Option<RecurseOptions>,\n\n    /// How to sort and filter the files after getting their details.\n    pub filter: &'a FileFilter,\n\n    /// Whether we are skipping Git-ignored files.\n    pub git_ignoring: bool,\n\n    pub git: Option<&'a GitCache>,\n}\n\n\nstruct Egg<'a> {\n    table_row: Option<TableRow>,\n    xattrs:    Vec<Attribute>,\n    errors:    Vec<(io::Error, Option<PathBuf>)>,\n    dir:       Option<Dir>,\n    file:      &'a File<'a>,\n}\n\nimpl<'a> AsRef<File<'a>> for Egg<'a> {\n    fn as_ref(&self) -> &File<'a> {\n        self.file\n    }\n}\n\n\nimpl<'a> Render<'a> {\n    pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {\n        let n_cpus = match num_cpus::get() as u32 {\n            0 => 1,\n            n => n,\n        };\n        let mut pool = Pool::new(n_cpus);\n        let mut rows = Vec::new();\n\n        if let Some(ref table) = self.opts.table {\n            match (self.git, self.dir) {\n                (Some(g), Some(d))  => if ! g.has_anything_for(&d.path) { self.git = None },\n                (Some(g), None)     => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { self.git = None },\n                (None,    _)        => {/* Keep Git how it is */},\n            }\n\n            let mut table = Table::new(table, self.git, self.theme);\n\n            if self.opts.header {\n                let header = table.header_row();\n                table.add_widths(&header);\n                rows.push(self.render_header(header));\n            }\n\n            // This is weird, but I can’t find a way around it:\n            // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6\n            let mut table = Some(table);\n            self.add_files_to_table(&mut pool, &mut table, &mut rows, &self.files, TreeDepth::root());\n\n            for row in self.iterate_with_table(table.unwrap(), rows) {\n                writeln!(w, \"{}\", row.strings())?\n            }\n        }\n        else {\n            self.add_files_to_table(&mut pool, &mut None, &mut rows, &self.files, TreeDepth::root());\n\n            for row in self.iterate(rows) {\n                writeln!(w, \"{}\", row.strings())?\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Adds files to the table, possibly recursively. This is easily\n    /// parallelisable, and uses a pool of threads.\n    fn add_files_to_table<'dir>(&self, pool: &mut Pool, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &[File<'dir>], depth: TreeDepth) {\n        use std::sync::{Arc, Mutex};\n        use log::*;\n        use crate::fs::feature::xattr;\n\n        let mut file_eggs = (0..src.len()).map(|_| MaybeUninit::uninit()).collect::<Vec<_>>();\n\n        pool.scoped(|scoped| {\n            let file_eggs = Arc::new(Mutex::new(&mut file_eggs));\n            let table = table.as_ref();\n\n            for (idx, file) in src.iter().enumerate() {\n                let file_eggs = Arc::clone(&file_eggs);\n\n                scoped.execute(move || {\n                    let mut errors = Vec::new();\n                    let mut xattrs = Vec::new();\n\n                    // There are three “levels” of extended attribute support:\n                    //\n                    // 1. If we’re compiling without that feature, then\n                    //    exa pretends all files have no attributes.\n                    // 2. If the feature is enabled and the --extended flag\n                    //    has been specified, then display an @ in the\n                    //    permissions column for files with attributes, the\n                    //    names of all attributes and their lengths, and any\n                    //    errors encountered when getting them.\n                    // 3. If the --extended flag *hasn’t* been specified, then\n                    //    display the @, but don’t display anything else.\n                    //\n                    // For a while, exa took a stricter approach to (3):\n                    // if an error occurred while checking a file’s xattrs to\n                    // see if it should display the @, exa would display that\n                    // error even though the attributes weren’t actually being\n                    // shown! This was confusing, as users were being shown\n                    // errors for something they didn’t explicitly ask for,\n                    // and just cluttered up the output. So now errors aren’t\n                    // printed unless the user passes --extended to signify\n                    // that they want to see them.\n\n                    if xattr::ENABLED {\n                        match file.path.attributes() {\n                            Ok(xs) => {\n                                xattrs.extend(xs);\n                            }\n                            Err(e) => {\n                                if self.opts.xattr {\n                                    errors.push((e, None));\n                                }\n                                else {\n                                    error!(\"Error looking up xattr for {:?}: {:#?}\", file.path, e);\n                                }\n                            }\n                        }\n                    }\n\n                    let table_row = table.as_ref()\n                                         .map(|t| t.row_for_file(file, ! xattrs.is_empty()));\n\n                    if ! self.opts.xattr {\n                        xattrs.clear();\n                    }\n\n                    let mut dir = None;\n                    if let Some(r) = self.recurse {\n                        if file.is_directory() && r.tree && ! r.is_too_deep(depth.0) {\n                            match file.to_dir() {\n                                Ok(d) => {\n                                    dir = Some(d);\n                                }\n                                Err(e) => {\n                                    errors.push((e, None));\n                                }\n                            }\n                        }\n                    };\n\n                    let egg = Egg { table_row, xattrs, errors, dir, file };\n                    unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) }\n                });\n            }\n        });\n\n        // this is safe because all entries have been initialized above\n        let mut file_eggs = unsafe { std::mem::transmute::<_, Vec<Egg<'_>>>(file_eggs) };\n        self.filter.sort_files(&mut file_eggs);\n\n        for (tree_params, egg) in depth.iterate_over(file_eggs.into_iter()) {\n            let mut files = Vec::new();\n            let mut errors = egg.errors;\n\n            if let (Some(ref mut t), Some(row)) = (table.as_mut(), egg.table_row.as_ref()) {\n                t.add_widths(row);\n            }\n\n            let file_name = self.file_style.for_file(egg.file, self.theme)\n                                .with_link_paths()\n                                .paint()\n                                .promote();\n\n            let row = Row {\n                tree:   tree_params,\n                cells:  egg.table_row,\n                name:   file_name,\n            };\n\n            rows.push(row);\n\n            if let Some(ref dir) = egg.dir {\n                for file_to_add in dir.files(self.filter.dot_filter, self.git, self.git_ignoring) {\n                    match file_to_add {\n                        Ok(f) => {\n                            files.push(f);\n                        }\n                        Err((path, e)) => {\n                            errors.push((e, Some(path)));\n                        }\n                    }\n                }\n\n                self.filter.filter_child_files(&mut files);\n\n                if ! files.is_empty() {\n                    for xattr in egg.xattrs {\n                        rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), false)));\n                    }\n\n                    for (error, path) in errors {\n                        rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path));\n                    }\n\n                    self.add_files_to_table(pool, table, rows, &files, depth.deeper());\n                    continue;\n                }\n            }\n\n            let count = egg.xattrs.len();\n            for (index, xattr) in egg.xattrs.into_iter().enumerate() {\n                let params = TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1);\n                let r = self.render_xattr(&xattr, params);\n                rows.push(r);\n            }\n\n            let count = errors.len();\n            for (index, (error, path)) in errors.into_iter().enumerate() {\n                let params = TreeParams::new(depth.deeper(), index == count - 1);\n                let r = self.render_error(&error, params, path);\n                rows.push(r);\n            }\n        }\n    }\n\n    pub fn render_header(&self, header: TableRow) -> Row {\n        Row {\n            tree:     TreeParams::new(TreeDepth::root(), false),\n            cells:    Some(header),\n            name:     TextCell::paint_str(self.theme.ui.header, \"Name\"),\n        }\n    }\n\n    fn render_error(&self, error: &io::Error, tree: TreeParams, path: Option<PathBuf>) -> Row {\n        use crate::output::file_name::Colours;\n\n        let error_message = if let Some(path) = path {\n            format!(\"<{}: {}>\", path.display(), error)\n        } else {\n            format!(\"<{}>\", error)\n        };\n\n        // TODO: broken_symlink() doesn’t quite seem like the right name for\n        // the style that’s being used here. Maybe split it in two?\n        let name = TextCell::paint(self.theme.broken_symlink(), error_message);\n        Row { cells: None, name, tree }\n    }\n\n    fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row {\n        let name = TextCell::paint(self.theme.ui.perms.attribute, format!(\"{} (len {})\", xattr.name, xattr.size));\n        Row { cells: None, name, tree }\n    }\n\n    pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreeParams) -> Row {\n        Row { cells: Some(cells), name, tree }\n    }\n\n    pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec<Row>) -> TableIter<'a> {\n        TableIter {\n            tree_trunk: TreeTrunk::default(),\n            total_width: table.widths().total(),\n            table,\n            inner: rows.into_iter(),\n            tree_style: self.theme.ui.punctuation,\n        }\n    }\n\n    pub fn iterate(&'a self, rows: Vec<Row>) -> Iter {\n        Iter {\n            tree_trunk: TreeTrunk::default(),\n            inner: rows.into_iter(),\n            tree_style: self.theme.ui.punctuation,\n        }\n    }\n}\n\n\npub struct Row {\n\n    /// Vector of cells to display.\n    ///\n    /// Most of the rows will be used to display files’ metadata, so this will\n    /// almost always be `Some`, containing a vector of cells. It will only be\n    /// `None` for a row displaying an attribute or error, neither of which\n    /// have cells.\n    pub cells: Option<TableRow>,\n\n    /// This file’s name, in coloured output. The name is treated separately\n    /// from the other cells, as it never requires padding.\n    pub name: TextCell,\n\n    /// Information used to determine which symbols to display in a tree.\n    pub tree: TreeParams,\n}\n\n\npub struct TableIter<'a> {\n    inner: VecIntoIter<Row>,\n    table: Table<'a>,\n\n    total_width: usize,\n    tree_style:  Style,\n    tree_trunk:  TreeTrunk,\n}\n\nimpl<'a> Iterator for TableIter<'a> {\n    type Item = TextCell;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.inner.next().map(|row| {\n            let mut cell =\n                if let Some(cells) = row.cells {\n                    self.table.render(cells)\n                }\n                else {\n                    let mut cell = TextCell::default();\n                    cell.add_spaces(self.total_width);\n                    cell\n                };\n\n            for tree_part in self.tree_trunk.new_row(row.tree) {\n                cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);\n            }\n\n            // If any tree characters have been printed, then add an extra\n            // space, which makes the output look much better.\n            if ! row.tree.is_at_root() {\n                cell.add_spaces(1);\n            }\n\n            cell.append(row.name);\n            cell\n        })\n    }\n}\n\n\npub struct Iter {\n    tree_trunk: TreeTrunk,\n    tree_style: Style,\n    inner: VecIntoIter<Row>,\n}\n\nimpl Iterator for Iter {\n    type Item = TextCell;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.inner.next().map(|row| {\n            let mut cell = TextCell::default();\n\n            for tree_part in self.tree_trunk.new_row(row.tree) {\n                cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);\n            }\n\n            // If any tree characters have been printed, then add an extra\n            // space, which makes the output look much better.\n            if ! row.tree.is_at_root() {\n                cell.add_spaces(1);\n            }\n\n            cell.append(row.name);\n            cell\n        })\n    }\n}\n"
  },
  {
    "path": "src/output/escape.rs",
    "content": "use ansi_term::{ANSIString, Style};\n\n\npub fn escape(string: String, bits: &mut Vec<ANSIString<'_>>, good: Style, bad: Style) {\n    if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) {\n        bits.push(good.paint(string));\n        return;\n    }\n\n    for c in string.chars() {\n        // The `escape_default` method on `char` is *almost* what we want here, but\n        // it still escapes non-ASCII UTF-8 characters, which are still printable.\n\n        if c >= 0x20 as char && c != 0x7f as char {\n            // TODO: This allocates way too much,\n            // hence the `all` check above.\n            let mut s = String::new();\n            s.push(c);\n            bits.push(good.paint(s));\n        }\n        else {\n            let s = c.escape_default().collect::<String>();\n            bits.push(bad.paint(s));\n        }\n    }\n}\n"
  },
  {
    "path": "src/output/file_name.rs",
    "content": "use std::fmt::Debug;\nuse std::path::Path;\n\nuse ansi_term::{ANSIString, Style};\n\nuse crate::fs::{File, FileTarget};\nuse crate::output::cell::TextCellContents;\nuse crate::output::escape;\nuse crate::output::icons::{icon_for_file, iconify_style};\nuse crate::output::render::FiletypeColours;\n\n\n/// Basically a file name factory.\n#[derive(Debug, Copy, Clone)]\npub struct Options {\n\n    /// Whether to append file class characters to file names.\n    pub classify: Classify,\n\n    /// Whether to prepend icon characters before file names.\n    pub show_icons: ShowIcons,\n}\n\nimpl Options {\n\n    /// Create a new `FileName` that prints the given file’s name, painting it\n    /// with the remaining arguments.\n    pub fn for_file<'a, 'dir, C>(self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> {\n        FileName {\n            file,\n            colours,\n            link_style: LinkStyle::JustFilenames,\n            options:    self,\n            target:     if file.is_link() { Some(file.link_target()) }\n                                     else { None }\n        }\n    }\n}\n\n/// When displaying a file name, there needs to be some way to handle broken\n/// links, depending on how long the resulting Cell can be.\n#[derive(PartialEq, Debug, Copy, Clone)]\nenum LinkStyle {\n\n    /// Just display the file names, but colour them differently if they’re\n    /// a broken link or can’t be followed.\n    JustFilenames,\n\n    /// Display all files in their usual style, but follow each link with an\n    /// arrow pointing to their path, colouring the path differently if it’s\n    /// a broken link, and doing nothing if it can’t be followed.\n    FullLinkPaths,\n}\n\n\n/// Whether to append file class characters to the file names.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum Classify {\n\n    /// Just display the file names, without any characters.\n    JustFilenames,\n\n    /// Add a character after the file name depending on what class of file\n    /// it is.\n    AddFileIndicators,\n}\n\nimpl Default for Classify {\n    fn default() -> Self {\n        Self::JustFilenames\n    }\n}\n\n\n/// Whether and how to show icons.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum ShowIcons {\n\n    /// Don’t show icons at all.\n    Off,\n\n    /// Show icons next to file names, with the given number of spaces between\n    /// the icon and the file name.\n    On(u32),\n}\n\n\n/// A **file name** holds all the information necessary to display the name\n/// of the given file. This is used in all of the views.\npub struct FileName<'a, 'dir, C> {\n\n    /// A reference to the file that we’re getting the name of.\n    file: &'a File<'dir>,\n\n    /// The colours used to paint the file name and its surrounding text.\n    colours: &'a C,\n\n    /// The file that this file points to if it’s a link.\n    target: Option<FileTarget<'dir>>,  // todo: remove?\n\n    /// How to handle displaying links.\n    link_style: LinkStyle,\n\n    options: Options,\n}\n\nimpl<'a, 'dir, C> FileName<'a, 'dir, C> {\n\n    /// Sets the flag on this file name to display link targets with an\n    /// arrow followed by their path.\n    pub fn with_link_paths(mut self) -> Self {\n        self.link_style = LinkStyle::FullLinkPaths;\n        self\n    }\n}\n\nimpl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {\n\n    /// Paints the name of the file using the colours, resulting in a vector\n    /// of coloured cells that can be printed to the terminal.\n    ///\n    /// This method returns some `TextCellContents`, rather than a `TextCell`,\n    /// because for the last cell in a table, it doesn’t need to have its\n    /// width calculated.\n    pub fn paint(&self) -> TextCellContents {\n        let mut bits = Vec::new();\n\n        if let ShowIcons::On(spaces_count) = self.options.show_icons {\n            let style = iconify_style(self.style());\n            let file_icon = icon_for_file(self.file).to_string();\n\n            bits.push(style.paint(file_icon));\n\n            match spaces_count {\n                1 => bits.push(style.paint(\" \")),\n                2 => bits.push(style.paint(\"  \")),\n                n => bits.push(style.paint(spaces(n))),\n            }\n        }\n\n        if self.file.parent_dir.is_none() {\n            if let Some(parent) = self.file.path.parent() {\n                self.add_parent_bits(&mut bits, parent);\n            }\n        }\n\n        if ! self.file.name.is_empty() {\n        \t// The “missing file” colour seems like it should be used here,\n        \t// but it’s not! In a grid view, where there’s no space to display\n        \t// link targets, the filename has to have a different style to\n        \t// indicate this fact. But when showing targets, we can just\n        \t// colour the path instead (see below), and leave the broken\n        \t// link’s filename as the link colour.\n            for bit in self.coloured_file_name() {\n                bits.push(bit);\n            }\n        }\n\n        if let (LinkStyle::FullLinkPaths, Some(target)) = (self.link_style, self.target.as_ref()) {\n            match target {\n                FileTarget::Ok(target) => {\n                    bits.push(Style::default().paint(\" \"));\n                    bits.push(self.colours.normal_arrow().paint(\"->\"));\n                    bits.push(Style::default().paint(\" \"));\n\n                    if let Some(parent) = target.path.parent() {\n                        self.add_parent_bits(&mut bits, parent);\n                    }\n\n                    if ! target.name.is_empty() {\n                        let target_options = Options {\n                            classify: Classify::JustFilenames,\n                            show_icons: ShowIcons::Off,\n                        };\n\n                        let target_name = FileName {\n                            file: target,\n                            colours: self.colours,\n                            target: None,\n                            link_style: LinkStyle::FullLinkPaths,\n                            options: target_options,\n                        };\n\n                        for bit in target_name.coloured_file_name() {\n                            bits.push(bit);\n                        }\n\n                        if let Classify::AddFileIndicators = self.options.classify {\n                            if let Some(class) = self.classify_char(target) {\n                                bits.push(Style::default().paint(class));\n                            }\n                        }\n                    }\n                }\n\n                FileTarget::Broken(broken_path) => {\n                    bits.push(Style::default().paint(\" \"));\n                    bits.push(self.colours.broken_symlink().paint(\"->\"));\n                    bits.push(Style::default().paint(\" \"));\n\n                    escape(\n                        broken_path.display().to_string(),\n                        &mut bits,\n                        self.colours.broken_filename(),\n                        self.colours.broken_control_char(),\n                    );\n                }\n\n                FileTarget::Err(_) => {\n                    // Do nothing — the error gets displayed on the next line\n                }\n            }\n        }\n        else if let Classify::AddFileIndicators = self.options.classify {\n            if let Some(class) = self.classify_char(self.file) {\n                bits.push(Style::default().paint(class));\n            }\n        }\n\n        bits.into()\n    }\n\n    /// Adds the bits of the parent path to the given bits vector.\n    /// The path gets its characters escaped based on the colours.\n    fn add_parent_bits(&self, bits: &mut Vec<ANSIString<'_>>, parent: &Path) {\n        let coconut = parent.components().count();\n\n        if coconut == 1 && parent.has_root() {\n            bits.push(self.colours.symlink_path().paint(std::path::MAIN_SEPARATOR.to_string()));\n        }\n        else if coconut >= 1 {\n            escape(\n                parent.to_string_lossy().to_string(),\n                bits,\n                self.colours.symlink_path(),\n                self.colours.control_char(),\n            );\n            bits.push(self.colours.symlink_path().paint(std::path::MAIN_SEPARATOR.to_string()));\n        }\n    }\n\n    /// The character to be displayed after a file when classifying is on, if\n    /// the file’s type has one associated with it.\n    #[cfg(unix)]\n    fn classify_char(&self, file: &File<'_>) -> Option<&'static str> {\n        if file.is_executable_file() {\n            Some(\"*\")\n        }\n        else if file.is_directory() {\n            Some(\"/\")\n        }\n        else if file.is_pipe() {\n            Some(\"|\")\n        }\n        else if file.is_link() {\n            Some(\"@\")\n        }\n        else if file.is_socket() {\n            Some(\"=\")\n        }\n        else {\n            None\n        }\n    }\n\n    #[cfg(windows)]\n    fn classify_char(&self, file: &File<'_>) -> Option<&'static str> {\n        if file.is_directory() {\n            Some(\"/\")\n        }\n        else if file.is_link() {\n            Some(\"@\")\n        }\n        else {\n            None\n        }\n    }\n\n    /// Returns at least one ANSI-highlighted string representing this file’s\n    /// name using the given set of colours.\n    ///\n    /// Ordinarily, this will be just one string: the file’s complete name,\n    /// coloured according to its file type. If the name contains control\n    /// characters such as newlines or escapes, though, we can’t just print them\n    /// to the screen directly, because then there’ll be newlines in weird places.\n    ///\n    /// So in that situation, those characters will be escaped and highlighted in\n    /// a different colour.\n    fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {\n        let file_style = self.style();\n        let mut bits = Vec::new();\n\n        escape(\n            self.file.name.clone(),\n            &mut bits,\n            file_style,\n            self.colours.control_char(),\n        );\n\n        bits\n    }\n\n    /// Figures out which colour to paint the filename part of the output,\n    /// depending on which “type” of file it appears to be — either from the\n    /// class on the filesystem or from its name. (Or the broken link colour,\n    /// if there’s nowhere else for that fact to be shown.)\n    pub fn style(&self) -> Style {\n        if let LinkStyle::JustFilenames = self.link_style {\n            if let Some(ref target) = self.target {\n                if target.is_broken() {\n                    return self.colours.broken_symlink();\n                }\n            }\n        }\n\n        match self.file {\n            f if f.is_directory()        => self.colours.directory(),\n            #[cfg(unix)]\n            f if f.is_executable_file()  => self.colours.executable_file(),\n            f if f.is_link()             => self.colours.symlink(),\n            #[cfg(unix)]\n            f if f.is_pipe()             => self.colours.pipe(),\n            #[cfg(unix)]\n            f if f.is_block_device()     => self.colours.block_device(),\n            #[cfg(unix)]\n            f if f.is_char_device()      => self.colours.char_device(),\n            #[cfg(unix)]\n            f if f.is_socket()           => self.colours.socket(),\n            f if ! f.is_file()           => self.colours.special(),\n            _                            => self.colours.colour_file(self.file),\n        }\n    }\n}\n\n\n/// The set of colours that are needed to paint a file name.\npub trait Colours: FiletypeColours {\n\n    /// The style to paint the path of a symlink’s target, up to but not\n    /// including the file’s name.\n    fn symlink_path(&self) -> Style;\n\n    /// The style to paint the arrow between a link and its target.\n    fn normal_arrow(&self) -> Style;\n\n\t/// The style to paint the filenames of broken links in views that don’t\n\t/// show link targets, and the style to paint the *arrow* between the link\n\t/// and its target in views that *do* show link targets.\n    fn broken_symlink(&self) -> Style;\n\n    /// The style to paint the entire filename of a broken link.\n    fn broken_filename(&self) -> Style;\n\n    /// The style to paint a non-displayable control character in a filename.\n    fn control_char(&self) -> Style;\n\n    /// The style to paint a non-displayable control character in a filename,\n    /// when the filename is being displayed as a broken link target.\n    fn broken_control_char(&self) -> Style;\n\n    /// The style to paint a file that has its executable bit set.\n    fn executable_file(&self) -> Style;\n\n    fn colour_file(&self, file: &File<'_>) -> Style;\n}\n\n\n/// Generate a string made of `n` spaces.\nfn spaces(width: u32) -> String {\n    (0 .. width).into_iter().map(|_| ' ').collect()\n}\n"
  },
  {
    "path": "src/output/grid.rs",
    "content": "use std::io::{self, Write};\n\nuse term_grid as tg;\n\nuse crate::fs::File;\nuse crate::fs::filter::FileFilter;\nuse crate::output::file_name::Options as FileStyle;\nuse crate::theme::Theme;\n\n\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub struct Options {\n    pub across: bool,\n}\n\nimpl Options {\n    pub fn direction(self) -> tg::Direction {\n        if self.across { tg::Direction::LeftToRight }\n                  else { tg::Direction::TopToBottom }\n    }\n}\n\n\npub struct Render<'a> {\n    pub files: Vec<File<'a>>,\n    pub theme: &'a Theme,\n    pub file_style: &'a FileStyle,\n    pub opts: &'a Options,\n    pub console_width: usize,\n    pub filter: &'a FileFilter,\n}\n\nimpl<'a> Render<'a> {\n    pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {\n        let mut grid = tg::Grid::new(tg::GridOptions {\n            direction:  self.opts.direction(),\n            filling:    tg::Filling::Spaces(2),\n        });\n\n        grid.reserve(self.files.len());\n\n        self.filter.sort_files(&mut self.files);\n        for file in &self.files {\n            let filename = self.file_style.for_file(file, self.theme).paint();\n\n            grid.add(tg::Cell {\n                contents:  filename.strings().to_string(),\n                width:     *filename.width(),\n                alignment: tg::Alignment::Left,\n            });\n        }\n\n        if let Some(display) = grid.fit_into_width(self.console_width) {\n            write!(w, \"{}\", display)\n        }\n        else {\n            // File names too long for a grid - drop down to just listing them!\n            // This isn’t *quite* the same as the lines view, which also\n            // displays full link paths.\n            for file in &self.files {\n                let name_cell = self.file_style.for_file(file, self.theme).paint();\n                writeln!(w, \"{}\", name_cell.strings())?;\n            }\n\n            Ok(())\n        }\n    }\n}\n"
  },
  {
    "path": "src/output/grid_details.rs",
    "content": "//! The grid-details view lists several details views side-by-side.\n\nuse std::io::{self, Write};\n\nuse ansi_term::ANSIStrings;\nuse term_grid as grid;\n\nuse crate::fs::{Dir, File};\nuse crate::fs::feature::git::GitCache;\nuse crate::fs::feature::xattr::FileAttributes;\nuse crate::fs::filter::FileFilter;\nuse crate::output::cell::TextCell;\nuse crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};\nuse crate::output::file_name::Options as FileStyle;\nuse crate::output::grid::Options as GridOptions;\nuse crate::output::table::{Table, Row as TableRow, Options as TableOptions};\nuse crate::output::tree::{TreeParams, TreeDepth};\nuse crate::theme::Theme;\n\n\n#[derive(PartialEq, Eq, Debug)]\npub struct Options {\n    pub grid: GridOptions,\n    pub details: DetailsOptions,\n    pub row_threshold: RowThreshold,\n}\n\nimpl Options {\n    pub fn to_details_options(&self) -> &DetailsOptions {\n        &self.details\n    }\n}\n\n\n/// The grid-details view can be configured to revert to just a details view\n/// (with one column) if it wouldn’t produce enough rows of output.\n///\n/// Doing this makes the resulting output look a bit better: when listing a\n/// small directory of four files in four columns, the files just look spaced\n/// out and it’s harder to see what’s going on. So it can be enabled just for\n/// larger directory listings.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum RowThreshold {\n\n    /// Only use grid-details view if it would result in at least this many\n    /// rows of output.\n    MinimumRows(usize),\n\n    /// Use the grid-details view no matter what.\n    AlwaysGrid,\n}\n\n\npub struct Render<'a> {\n\n    /// The directory that’s being rendered here.\n    /// We need this to know which columns to put in the output.\n    pub dir: Option<&'a Dir>,\n\n    /// The files that have been read from the directory. They should all\n    /// hold a reference to it.\n    pub files: Vec<File<'a>>,\n\n    /// How to colour various pieces of text.\n    pub theme: &'a Theme,\n\n    /// How to format filenames.\n    pub file_style: &'a FileStyle,\n\n    /// The grid part of the grid-details view.\n    pub grid: &'a GridOptions,\n\n    /// The details part of the grid-details view.\n    pub details: &'a DetailsOptions,\n\n    /// How to filter files after listing a directory. The files in this\n    /// render will already have been filtered and sorted, but any directories\n    /// that we recurse into will have to have this applied.\n    pub filter: &'a FileFilter,\n\n    /// The minimum number of rows that there need to be before grid-details\n    /// mode is activated.\n    pub row_threshold: RowThreshold,\n\n    /// Whether we are skipping Git-ignored files.\n    pub git_ignoring: bool,\n\n    pub git: Option<&'a GitCache>,\n\n    pub console_width: usize,\n}\n\nimpl<'a> Render<'a> {\n\n    /// Create a temporary Details render that gets used for the columns of\n    /// the grid-details render that’s being generated.\n    ///\n    /// This includes an empty files vector because the files get added to\n    /// the table in *this* file, not in details: we only want to insert every\n    /// *n* files into each column’s table, not all of them.\n    fn details_for_column(&self) -> DetailsRender<'a> {\n        DetailsRender {\n            dir:           self.dir,\n            files:         Vec::new(),\n            theme:         self.theme,\n            file_style:    self.file_style,\n            opts:          self.details,\n            recurse:       None,\n            filter:        self.filter,\n            git_ignoring:  self.git_ignoring,\n            git:           self.git,\n        }\n    }\n\n    /// Create a Details render for when this grid-details render doesn’t fit\n    /// in the terminal (or something has gone wrong) and we have given up, or\n    /// when the user asked for a grid-details view but the terminal width is\n    /// not available, so we downgrade.\n    pub fn give_up(self) -> DetailsRender<'a> {\n        DetailsRender {\n            dir:           self.dir,\n            files:         self.files,\n            theme:         self.theme,\n            file_style:    self.file_style,\n            opts:          self.details,\n            recurse:       None,\n            filter:        self.filter,\n            git_ignoring:  self.git_ignoring,\n            git:           self.git,\n        }\n    }\n\n    // This doesn’t take an IgnoreCache even though the details one does\n    // because grid-details has no tree view.\n\n    pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {\n        if let Some((grid, width)) = self.find_fitting_grid() {\n            write!(w, \"{}\", grid.fit_into_columns(width))\n        }\n        else {\n            self.give_up().render(w)\n        }\n    }\n\n    pub fn find_fitting_grid(&mut self) -> Option<(grid::Grid, grid::Width)> {\n        let options = self.details.table.as_ref().expect(\"Details table options not given!\");\n\n        let drender = self.details_for_column();\n\n        let (first_table, _) = self.make_table(options, &drender);\n\n        let rows = self.files.iter()\n                       .map(|file| first_table.row_for_file(file, file_has_xattrs(file)))\n                       .collect::<Vec<_>>();\n\n        let file_names = self.files.iter()\n                             .map(|file| self.file_style.for_file(file, self.theme).paint().promote())\n                             .collect::<Vec<_>>();\n\n        let mut last_working_grid = self.make_grid(1, options, &file_names, rows.clone(), &drender);\n\n        if file_names.len() == 1 {\n            return Some((last_working_grid, 1));\n        }\n\n        // If we can’t fit everything in a grid 100 columns wide, then\n        // something has gone seriously awry\n        for column_count in 2..100 {\n            let grid = self.make_grid(column_count, options, &file_names, rows.clone(), &drender);\n\n            let the_grid_fits = {\n                let d = grid.fit_into_columns(column_count);\n                d.width() <= self.console_width\n            };\n\n            if the_grid_fits {\n                last_working_grid = grid;\n            }\n\n            if !the_grid_fits || column_count == file_names.len() {\n                let last_column_count = if the_grid_fits { column_count } else { column_count - 1 };\n                // If we’ve figured out how many columns can fit in the user’s terminal,\n                // and it turns out there aren’t enough rows to make it worthwhile\n                // (according to EXA_GRID_ROWS), then just resort to the lines view.\n                if let RowThreshold::MinimumRows(thresh) = self.row_threshold {\n                    if last_working_grid.fit_into_columns(last_column_count).row_count() < thresh {\n                        return None;\n                    }\n                }\n\n                return Some((last_working_grid, last_column_count));\n            }\n        }\n\n        None\n    }\n\n    fn make_table(&mut self, options: &'a TableOptions, drender: &DetailsRender<'_>) -> (Table<'a>, Vec<DetailsRow>) {\n        match (self.git, self.dir) {\n            (Some(g), Some(d))  => if ! g.has_anything_for(&d.path) { self.git = None },\n            (Some(g), None)     => if ! self.files.iter().any(|f| g.has_anything_for(&f.path)) { self.git = None },\n            (None,    _)        => {/* Keep Git how it is */},\n        }\n\n        let mut table = Table::new(options, self.git, self.theme);\n        let mut rows = Vec::new();\n\n        if self.details.header {\n            let row = table.header_row();\n            table.add_widths(&row);\n            rows.push(drender.render_header(row));\n        }\n\n        (table, rows)\n    }\n\n    fn make_grid(&mut self, column_count: usize, options: &'a TableOptions, file_names: &[TextCell], rows: Vec<TableRow>, drender: &DetailsRender<'_>) -> grid::Grid {\n        let mut tables = Vec::new();\n        for _ in 0 .. column_count {\n            tables.push(self.make_table(options, drender));\n        }\n\n        let mut num_cells = rows.len();\n        if self.details.header {\n            num_cells += column_count;\n        }\n\n        let original_height = divide_rounding_up(rows.len(), column_count);\n        let height = divide_rounding_up(num_cells, column_count);\n\n        for (i, (file_name, row)) in file_names.iter().zip(rows.into_iter()).enumerate() {\n            let index = if self.grid.across {\n                    i % column_count\n                }\n                else {\n                    i / original_height\n                };\n\n            let (ref mut table, ref mut rows) = tables[index];\n            table.add_widths(&row);\n            let details_row = drender.render_file(row, file_name.clone(), TreeParams::new(TreeDepth::root(), false));\n            rows.push(details_row);\n        }\n\n        let columns = tables\n            .into_iter()\n            .map(|(table, details_rows)| {\n                drender.iterate_with_table(table, details_rows)\n                       .collect::<Vec<_>>()\n                })\n            .collect::<Vec<_>>();\n\n        let direction = if self.grid.across { grid::Direction::LeftToRight }\n                                       else { grid::Direction::TopToBottom };\n\n        let filling = grid::Filling::Spaces(4);\n        let mut grid = grid::Grid::new(grid::GridOptions { direction, filling });\n\n        if self.grid.across {\n            for row in 0 .. height {\n                for column in &columns {\n                    if row < column.len() {\n                        let cell = grid::Cell {\n                            contents: ANSIStrings(&column[row].contents).to_string(),\n                            width:    *column[row].width,\n                            alignment: grid::Alignment::Left,\n                        };\n\n                        grid.add(cell);\n                    }\n                }\n            }\n        }\n        else {\n            for column in &columns {\n                for cell in column.iter() {\n                    let cell = grid::Cell {\n                        contents: ANSIStrings(&cell.contents).to_string(),\n                        width:    *cell.width,\n                        alignment: grid::Alignment::Left,\n                    };\n\n                    grid.add(cell);\n                }\n            }\n        }\n\n        grid\n    }\n}\n\n\nfn divide_rounding_up(a: usize, b: usize) -> usize {\n    let mut result = a / b;\n\n    if a % b != 0 {\n        result += 1;\n    }\n\n    result\n}\n\n\nfn file_has_xattrs(file: &File<'_>) -> bool {\n    match file.path.attributes() {\n        Ok(attrs)  => ! attrs.is_empty(),\n        Err(_)     => false,\n    }\n}\n"
  },
  {
    "path": "src/output/icons.rs",
    "content": "use ansi_term::Style;\n\nuse crate::fs::File;\nuse crate::info::filetype::FileExtensions;\nuse lazy_static::lazy_static;\nuse std::collections::HashMap;\n\n\npub trait FileIcon {\n    fn icon_file(&self, file: &File<'_>) -> Option<char>;\n}\n\n\n#[derive(Copy, Clone)]\npub enum Icons {\n    Audio,\n    Image,\n    Video,\n}\n\nimpl Icons {\n    pub fn value(self) -> char {\n        match self {\n            Self::Audio  => '\\u{f001}',\n            Self::Image  => '\\u{f1c5}',\n            Self::Video  => '\\u{f03d}',\n        }\n    }\n}\n\n\n/// Converts the style used to paint a file name into the style that should be\n/// used to paint an icon.\n///\n/// - The background colour should be preferred to the foreground colour, as\n///   if one is set, it’s the more “obvious” colour choice.\n/// - If neither is set, just use the default style.\n/// - Attributes such as bold or underline should not be used to paint the\n///   icon, as they can make it look weird.\npub fn iconify_style(style: Style) -> Style {\n    style.background.or(style.foreground)\n         .map(Style::from)\n         .unwrap_or_default()\n}\n\n\n\nlazy_static! {\n    static ref MAP_BY_NAME: HashMap<&'static str, char> = {\n        let mut m = HashMap::new();\n        m.insert(\".Trash\", '\\u{f1f8}'); // \n        m.insert(\".atom\", '\\u{e764}'); // \n        m.insert(\".bashprofile\", '\\u{e615}'); // \n        m.insert(\".bashrc\", '\\u{f489}'); // \n        m.insert(\".git\", '\\u{f1d3}'); // \n        m.insert(\".gitattributes\", '\\u{f1d3}'); // \n        m.insert(\".gitconfig\", '\\u{f1d3}'); // \n        m.insert(\".github\", '\\u{f408}'); // \n        m.insert(\".gitignore\", '\\u{f1d3}'); // \n        m.insert(\".gitmodules\", '\\u{f1d3}'); // \n        m.insert(\".rvm\", '\\u{e21e}'); // \n        m.insert(\".vimrc\", '\\u{e62b}'); // \n        m.insert(\".vscode\", '\\u{e70c}'); // \n        m.insert(\".zshrc\", '\\u{f489}'); // \n        m.insert(\"Cargo.lock\", '\\u{e7a8}'); // \n        m.insert(\"bin\", '\\u{e5fc}'); // \n        m.insert(\"config\", '\\u{e5fc}'); // \n        m.insert(\"docker-compose.yml\", '\\u{f308}'); // \n        m.insert(\"Dockerfile\", '\\u{f308}'); // \n        m.insert(\"ds_store\", '\\u{f179}'); // \n        m.insert(\"gitignore_global\", '\\u{f1d3}'); // \n        m.insert(\"go.mod\", '\\u{e626}'); // \n        m.insert(\"go.sum\", '\\u{e626}'); // \n        m.insert(\"gradle\", '\\u{e256}'); // \n        m.insert(\"gruntfile.coffee\", '\\u{e611}'); // \n        m.insert(\"gruntfile.js\", '\\u{e611}'); // \n        m.insert(\"gruntfile.ls\", '\\u{e611}'); // \n        m.insert(\"gulpfile.coffee\", '\\u{e610}'); // \n        m.insert(\"gulpfile.js\", '\\u{e610}'); // \n        m.insert(\"gulpfile.ls\", '\\u{e610}'); // \n        m.insert(\"hidden\", '\\u{f023}'); // \n        m.insert(\"include\", '\\u{e5fc}'); // \n        m.insert(\"lib\", '\\u{f121}'); // \n        m.insert(\"localized\", '\\u{f179}'); // \n        m.insert(\"Makefile\", '\\u{f489}'); // \n        m.insert(\"node_modules\", '\\u{e718}'); // \n        m.insert(\"npmignore\", '\\u{e71e}'); // \n        m.insert(\"PKGBUILD\", '\\u{f303}'); // \n        m.insert(\"rubydoc\", '\\u{e73b}'); // \n        m.insert(\"yarn.lock\", '\\u{e718}'); // \n\n        m\n    };\n}\n\npub fn icon_for_file(file: &File<'_>) -> char {\n    let extensions = Box::new(FileExtensions);\n\n    if let Some(icon) = MAP_BY_NAME.get(file.name.as_str()) { *icon }\n    else if file.points_to_directory() {\n        match file.name.as_str() {\n            \"bin\"           => '\\u{e5fc}', // \n            \".git\"          => '\\u{f1d3}', // \n            \".idea\"         => '\\u{e7b5}', // \n            _               => '\\u{f115}'  // \n        }\n    }\n    else if let Some(icon) = extensions.icon_file(file) { icon }\n    else if let Some(ext) = file.ext.as_ref() {\n        match ext.as_str() {\n            \"ai\"            => '\\u{e7b4}', // \n            \"android\"       => '\\u{e70e}', // \n            \"apk\"           => '\\u{e70e}', // \n            \"apple\"         => '\\u{f179}', // \n            \"avi\"           => '\\u{f03d}', // \n            \"avif\"          => '\\u{f1c5}', // \n            \"avro\"          => '\\u{e60b}', // \n            \"awk\"           => '\\u{f489}', // \n            \"bash\"          => '\\u{f489}', // \n            \"bash_history\"  => '\\u{f489}', // \n            \"bash_profile\"  => '\\u{f489}', // \n            \"bashrc\"        => '\\u{f489}', // \n            \"bat\"           => '\\u{f17a}', // \n            \"bats\"          => '\\u{f489}', // \n            \"bmp\"           => '\\u{f1c5}', // \n            \"bz\"            => '\\u{f410}', // \n            \"bz2\"           => '\\u{f410}', // \n            \"c\"             => '\\u{e61e}', // \n            \"c++\"           => '\\u{e61d}', // \n            \"cab\"           => '\\u{e70f}', // \n            \"cc\"            => '\\u{e61d}', // \n            \"cfg\"           => '\\u{e615}', // \n            \"class\"         => '\\u{e256}', // \n            \"clj\"           => '\\u{e768}', // \n            \"cljs\"          => '\\u{e76a}', // \n            \"cls\"           => '\\u{f034}', // \n            \"cmd\"           => '\\u{e70f}', // \n            \"coffee\"        => '\\u{f0f4}', // \n            \"conf\"          => '\\u{e615}', // \n            \"cp\"            => '\\u{e61d}', // \n            \"cpio\"          => '\\u{f410}', // \n            \"cpp\"           => '\\u{e61d}', // \n            \"cs\"            => '\\u{f031b}', // 󰌛\n            \"csh\"           => '\\u{f489}', // \n            \"cshtml\"        => '\\u{f1fa}', // \n            \"csproj\"        => '\\u{f031b}', // 󰌛\n            \"css\"           => '\\u{e749}', // \n            \"csv\"           => '\\u{f1c3}', // \n            \"csx\"           => '\\u{f031b}', // 󰌛\n            \"cxx\"           => '\\u{e61d}', // \n            \"d\"             => '\\u{e7af}', // \n            \"dart\"          => '\\u{e798}', // \n            \"db\"            => '\\u{f1c0}', // \n            \"deb\"           => '\\u{e77d}', // \n            \"diff\"          => '\\u{f440}', // \n            \"djvu\"          => '\\u{f02d}', // \n            \"dll\"           => '\\u{e70f}', // \n            \"doc\"           => '\\u{f1c2}', // \n            \"docx\"          => '\\u{f1c2}', // \n            \"ds_store\"      => '\\u{f179}', // \n            \"DS_store\"      => '\\u{f179}', // \n            \"dump\"          => '\\u{f1c0}', // \n            \"ebook\"         => '\\u{e28b}', // \n            \"ebuild\"        => '\\u{f30d}', // \n            \"editorconfig\"  => '\\u{e615}', // \n            \"ejs\"           => '\\u{e618}', // \n            \"elm\"           => '\\u{e62c}', // \n            \"env\"           => '\\u{f462}', // \n            \"eot\"           => '\\u{f031}', // \n            \"epub\"          => '\\u{e28a}', // \n            \"erb\"           => '\\u{e73b}', // \n            \"erl\"           => '\\u{e7b1}', // \n            \"ex\"            => '\\u{e62d}', // \n            \"exe\"           => '\\u{f17a}', // \n            \"exs\"           => '\\u{e62d}', // \n            \"fish\"          => '\\u{f489}', // \n            \"flac\"          => '\\u{f001}', // \n            \"flv\"           => '\\u{f03d}', // \n            \"font\"          => '\\u{f031}', // \n            \"fs\"            => '\\u{e7a7}', // \n            \"fsi\"           => '\\u{e7a7}', // \n            \"fsx\"           => '\\u{e7a7}', // \n            \"gdoc\"          => '\\u{f1c2}', // \n            \"gem\"           => '\\u{e21e}', // \n            \"gemfile\"       => '\\u{e21e}', // \n            \"gemspec\"       => '\\u{e21e}', // \n            \"gform\"         => '\\u{f298}', // \n            \"gif\"           => '\\u{f1c5}', // \n            \"git\"           => '\\u{f1d3}', // \n            \"gitattributes\" => '\\u{f1d3}', // \n            \"gitignore\"     => '\\u{f1d3}', // \n            \"gitmodules\"    => '\\u{f1d3}', // \n            \"go\"            => '\\u{e626}', // \n            \"gradle\"        => '\\u{e256}', // \n            \"groovy\"        => '\\u{e775}', // \n            \"gsheet\"        => '\\u{f1c3}', // \n            \"gslides\"       => '\\u{f1c4}', // \n            \"guardfile\"     => '\\u{e21e}', // \n            \"gz\"            => '\\u{f410}', // \n            \"h\"             => '\\u{f0fd}', // \n            \"hbs\"           => '\\u{e60f}', // \n            \"hpp\"           => '\\u{f0fd}', // \n            \"hs\"            => '\\u{e777}', // \n            \"htm\"           => '\\u{f13b}', // \n            \"html\"          => '\\u{f13b}', // \n            \"hxx\"           => '\\u{f0fd}', // \n            \"ico\"           => '\\u{f1c5}', // \n            \"image\"         => '\\u{f1c5}', // \n            \"img\"           => '\\u{e271}', // \n            \"iml\"           => '\\u{e7b5}', // \n            \"ini\"           => '\\u{f17a}', // \n            \"ipynb\"         => '\\u{e678}', // \n            \"iso\"           => '\\u{e271}', // \n            \"j2c\"           => '\\u{f1c5}', // \n            \"j2k\"           => '\\u{f1c5}', // \n            \"jad\"           => '\\u{e256}', // \n            \"jar\"           => '\\u{e256}', // \n            \"java\"          => '\\u{e256}', // \n            \"jfi\"           => '\\u{f1c5}', // \n            \"jfif\"          => '\\u{f1c5}', // \n            \"jif\"           => '\\u{f1c5}', // \n            \"jl\"            => '\\u{e624}', // \n            \"jmd\"           => '\\u{f48a}', // \n            \"jp2\"           => '\\u{f1c5}', // \n            \"jpe\"           => '\\u{f1c5}', // \n            \"jpeg\"          => '\\u{f1c5}', // \n            \"jpg\"           => '\\u{f1c5}', // \n            \"jpx\"           => '\\u{f1c5}', // \n            \"js\"            => '\\u{e74e}', // \n            \"json\"          => '\\u{e60b}', // \n            \"jsx\"           => '\\u{e7ba}', // \n            \"jxl\"           => '\\u{f1c5}', // \n            \"ksh\"           => '\\u{f489}', // \n            \"latex\"         => '\\u{f034}', // \n            \"less\"          => '\\u{e758}', // \n            \"lhs\"           => '\\u{e777}', // \n            \"license\"       => '\\u{f0219}', // 󰈙\n            \"localized\"     => '\\u{f179}', // \n            \"lock\"          => '\\u{f023}', // \n            \"log\"           => '\\u{f18d}', // \n            \"lua\"           => '\\u{e620}', // \n            \"lz\"            => '\\u{f410}', // \n            \"lz4\"           => '\\u{f410}', // \n            \"lzh\"           => '\\u{f410}', // \n            \"lzma\"          => '\\u{f410}', // \n            \"lzo\"           => '\\u{f410}', // \n            \"m\"             => '\\u{e61e}', // \n            \"mm\"            => '\\u{e61d}', // \n            \"m4a\"           => '\\u{f001}', // \n            \"markdown\"      => '\\u{f48a}', // \n            \"md\"            => '\\u{f48a}', // \n            \"mjs\"           => '\\u{e74e}', // \n            \"mk\"            => '\\u{f489}', // \n            \"mkd\"           => '\\u{f48a}', // \n            \"mkv\"           => '\\u{f03d}', // \n            \"mobi\"          => '\\u{e28b}', // \n            \"mov\"           => '\\u{f03d}', // \n            \"mp3\"           => '\\u{f001}', // \n            \"mp4\"           => '\\u{f03d}', // \n            \"msi\"           => '\\u{e70f}', // \n            \"mustache\"      => '\\u{e60f}', // \n            \"nix\"           => '\\u{f313}', // \n            \"node\"          => '\\u{f0399}', // 󰎙\n            \"npmignore\"     => '\\u{e71e}', // \n            \"odp\"           => '\\u{f1c4}', // \n            \"ods\"           => '\\u{f1c3}', // \n            \"odt\"           => '\\u{f1c2}', // \n            \"ogg\"           => '\\u{f001}', // \n            \"ogv\"           => '\\u{f03d}', // \n            \"otf\"           => '\\u{f031}', // \n            \"part\"          => '\\u{f43a}', // \n            \"patch\"         => '\\u{f440}', // \n            \"pdf\"           => '\\u{f1c1}', // \n            \"php\"           => '\\u{e73d}', // \n            \"pl\"            => '\\u{e769}', // \n            \"plx\"           => '\\u{e769}', // \n            \"pm\"            => '\\u{e769}', // \n            \"png\"           => '\\u{f1c5}', // \n            \"pod\"           => '\\u{e769}', // \n            \"ppt\"           => '\\u{f1c4}', // \n            \"pptx\"          => '\\u{f1c4}', // \n            \"procfile\"      => '\\u{e21e}', // \n            \"properties\"    => '\\u{e60b}', // \n            \"ps1\"           => '\\u{f489}', // \n            \"psd\"           => '\\u{e7b8}', // \n            \"pxm\"           => '\\u{f1c5}', // \n            \"py\"            => '\\u{e606}', // \n            \"pyc\"           => '\\u{e606}', // \n            \"r\"             => '\\u{f25d}', // \n            \"rakefile\"      => '\\u{e21e}', // \n            \"rar\"           => '\\u{f410}', // \n            \"razor\"         => '\\u{f1fa}', // \n            \"rb\"            => '\\u{e21e}', // \n            \"rdata\"         => '\\u{f25d}', // \n            \"rdb\"           => '\\u{e76d}', // \n            \"rdoc\"          => '\\u{f48a}', // \n            \"rds\"           => '\\u{f25d}', // \n            \"readme\"        => '\\u{f48a}', // \n            \"rlib\"          => '\\u{e7a8}', // \n            \"rmd\"           => '\\u{f48a}', // \n            \"rpm\"           => '\\u{e7bb}', // \n            \"rs\"            => '\\u{e7a8}', // \n            \"rspec\"         => '\\u{e21e}', // \n            \"rspec_parallel\"=> '\\u{e21e}', // \n            \"rspec_status\"  => '\\u{e21e}', // \n            \"rss\"           => '\\u{f09e}', // \n            \"rtf\"           => '\\u{f0219}', // 󰈙\n            \"ru\"            => '\\u{e21e}', // \n            \"rubydoc\"       => '\\u{e73b}', // \n            \"sass\"          => '\\u{e603}', // \n            \"scala\"         => '\\u{e737}', // \n            \"scss\"          => '\\u{e749}', // \n            \"sh\"            => '\\u{f489}', // \n            \"shell\"         => '\\u{f489}', // \n            \"slim\"          => '\\u{e73b}', // \n            \"sln\"           => '\\u{e70c}', // \n            \"so\"            => '\\u{f17c}', // \n            \"sql\"           => '\\u{f1c0}', // \n            \"sqlite3\"       => '\\u{e7c4}', // \n            \"sty\"           => '\\u{f034}', // \n            \"styl\"          => '\\u{e600}', // \n            \"stylus\"        => '\\u{e600}', // \n            \"svg\"           => '\\u{f1c5}', // \n            \"swift\"         => '\\u{e755}', // \n            \"t\"             => '\\u{e769}', // \n            \"tar\"           => '\\u{f410}', // \n            \"taz\"           => '\\u{f410}', // \n            \"tbz\"           => '\\u{f410}', // \n            \"tbz2\"          => '\\u{f410}', // \n            \"tex\"           => '\\u{f034}', // \n            \"tgz\"           => '\\u{f410}', // \n            \"tiff\"          => '\\u{f1c5}', // \n            \"tlz\"           => '\\u{f410}', // \n            \"toml\"          => '\\u{e615}', // \n            \"torrent\"       => '\\u{e275}', // \n            \"ts\"            => '\\u{e628}', // \n            \"tsv\"           => '\\u{f1c3}', // \n            \"tsx\"           => '\\u{e7ba}', // \n            \"ttf\"           => '\\u{f031}', // \n            \"twig\"          => '\\u{e61c}', // \n            \"txt\"           => '\\u{f15c}', // \n            \"txz\"           => '\\u{f410}', // \n            \"tz\"            => '\\u{f410}', // \n            \"tzo\"           => '\\u{f410}', // \n            \"video\"         => '\\u{f03d}', // \n            \"vim\"           => '\\u{e62b}', // \n            \"vue\"           => '\\u{f0844}', // 󰡄\n            \"war\"           => '\\u{e256}', // \n            \"wav\"           => '\\u{f001}', // \n            \"webm\"          => '\\u{f03d}', // \n            \"webp\"          => '\\u{f1c5}', // \n            \"windows\"       => '\\u{f17a}', // \n            \"woff\"          => '\\u{f031}', // \n            \"woff2\"         => '\\u{f031}', // \n            \"xhtml\"         => '\\u{f13b}', // \n            \"xls\"           => '\\u{f1c3}', // \n            \"xlsx\"          => '\\u{f1c3}', // \n            \"xml\"           => '\\u{f05c0}', // 󰗀\n            \"xul\"           => '\\u{f05c0}', // 󰗀\n            \"xz\"            => '\\u{f410}', // \n            \"yaml\"          => '\\u{f481}', // \n            \"yml\"           => '\\u{f481}', // \n            \"zip\"           => '\\u{f410}', // \n            \"zsh\"           => '\\u{f489}', // \n            \"zsh-theme\"     => '\\u{f489}', // \n            \"zshrc\"         => '\\u{f489}', // \n            \"zst\"           => '\\u{f410}', // \n            _               => '\\u{f15b}'  // \n        }\n    }\n    else {\n        '\\u{f016}'\n    }\n}\n"
  },
  {
    "path": "src/output/lines.rs",
    "content": "use std::io::{self, Write};\n\nuse ansi_term::ANSIStrings;\n\nuse crate::fs::File;\nuse crate::fs::filter::FileFilter;\nuse crate::output::cell::TextCellContents;\nuse crate::output::file_name::{Options as FileStyle};\nuse crate::theme::Theme;\n\n\n/// The lines view literally just displays each file, line-by-line.\npub struct Render<'a> {\n    pub files: Vec<File<'a>>,\n    pub theme: &'a Theme,\n    pub file_style: &'a FileStyle,\n    pub filter: &'a FileFilter,\n}\n\nimpl<'a> Render<'a> {\n    pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {\n        self.filter.sort_files(&mut self.files);\n        for file in &self.files {\n            let name_cell = self.render_file(file);\n            writeln!(w, \"{}\", ANSIStrings(&name_cell))?;\n        }\n\n        Ok(())\n    }\n\n    fn render_file<'f>(&self, file: &'f File<'a>) -> TextCellContents {\n        self.file_style\n            .for_file(file, self.theme)\n            .with_link_paths()\n            .paint()\n    }\n}\n"
  },
  {
    "path": "src/output/mod.rs",
    "content": "pub use self::cell::{TextCell, TextCellContents, DisplayWidth};\npub use self::escape::escape;\n\npub mod details;\npub mod file_name;\npub mod grid;\npub mod grid_details;\npub mod icons;\npub mod lines;\npub mod render;\npub mod table;\npub mod time;\n\nmod cell;\nmod escape;\nmod tree;\n\n\n/// The **view** contains all information about how to format output.\n#[derive(Debug)]\npub struct View {\n    pub mode: Mode,\n    pub width: TerminalWidth,\n    pub file_style: file_name::Options,\n}\n\n\n/// The **mode** is the “type” of output.\n#[derive(PartialEq, Eq, Debug)]\n#[allow(clippy::large_enum_variant)]\npub enum Mode {\n    Grid(grid::Options),\n    Details(details::Options),\n    GridDetails(grid_details::Options),\n    Lines,\n}\n\n\n/// The width of the terminal requested by the user.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum TerminalWidth {\n\n    /// The user requested this specific number of columns.\n    Set(usize),\n\n    /// Look up the terminal size at runtime.\n    Automatic,\n}\n\nimpl TerminalWidth {\n    pub fn actual_terminal_width(self) -> Option<usize> {\n        // All of stdin, stdout, and stderr could not be connected to a\n        // terminal, but we’re only interested in stdout because it’s\n        // where the output goes.\n\n        match self {\n            Self::Set(width)  => Some(width),\n            Self::Automatic   => terminal_size::terminal_size().map(|(w, _)| w.0.into()),\n        }\n    }\n}\n"
  },
  {
    "path": "src/output/render/blocks.rs",
    "content": "use ansi_term::Style;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\n\n\nimpl f::Blocks {\n    pub fn render<C: Colours>(&self, colours: &C) -> TextCell {\n        match self {\n            Self::Some(blk)  => TextCell::paint(colours.block_count(), blk.to_string()),\n            Self::None       => TextCell::blank(colours.no_blocks()),\n        }\n    }\n}\n\n\npub trait Colours {\n    fn block_count(&self) -> Style;\n    fn no_blocks(&self) -> Style;\n}\n\n\n#[cfg(test)]\npub mod test {\n    use ansi_term::Style;\n    use ansi_term::Colour::*;\n\n    use super::Colours;\n    use crate::output::cell::TextCell;\n    use crate::fs::fields as f;\n\n\n    struct TestColours;\n\n    impl Colours for TestColours {\n        fn block_count(&self) -> Style { Red.blink() }\n        fn no_blocks(&self)   -> Style { Green.italic() }\n    }\n\n\n    #[test]\n    fn blocklessness() {\n        let blox = f::Blocks::None;\n        let expected = TextCell::blank(Green.italic());\n\n        assert_eq!(expected, blox.render(&TestColours));\n    }\n\n\n    #[test]\n    fn blockfulity() {\n        let blox = f::Blocks::Some(3005);\n        let expected = TextCell::paint_str(Red.blink(), \"3005\");\n\n        assert_eq!(expected, blox.render(&TestColours));\n    }\n}\n"
  },
  {
    "path": "src/output/render/filetype.rs",
    "content": "use ansi_term::{ANSIString, Style};\n\nuse crate::fs::fields as f;\n\n\nimpl f::Type {\n    pub fn render<C: Colours>(self, colours: &C) -> ANSIString<'static> {\n        match self {\n            Self::File         => colours.normal().paint(\".\"),\n            Self::Directory    => colours.directory().paint(\"d\"),\n            Self::Pipe         => colours.pipe().paint(\"|\"),\n            Self::Link         => colours.symlink().paint(\"l\"),\n            Self::BlockDevice  => colours.block_device().paint(\"b\"),\n            Self::CharDevice   => colours.char_device().paint(\"c\"),\n            Self::Socket       => colours.socket().paint(\"s\"),\n            Self::Special      => colours.special().paint(\"?\"),\n        }\n    }\n}\n\n\npub trait Colours {\n    fn normal(&self) -> Style;\n    fn directory(&self) -> Style;\n    fn pipe(&self) -> Style;\n    fn symlink(&self) -> Style;\n    fn block_device(&self) -> Style;\n    fn char_device(&self) -> Style;\n    fn socket(&self) -> Style;\n    fn special(&self) -> Style;\n}\n"
  },
  {
    "path": "src/output/render/git.rs",
    "content": "use ansi_term::{ANSIString, Style};\n\nuse crate::output::cell::{TextCell, DisplayWidth};\nuse crate::fs::fields as f;\n\n\nimpl f::Git {\n    pub fn render(self, colours: &dyn Colours) -> TextCell {\n        TextCell {\n            width: DisplayWidth::from(2),\n            contents: vec![\n                self.staged.render(colours),\n                self.unstaged.render(colours),\n            ].into(),\n        }\n    }\n}\n\n\nimpl f::GitStatus {\n    fn render(self, colours: &dyn Colours) -> ANSIString<'static> {\n        match self {\n            Self::NotModified  => colours.not_modified().paint(\"-\"),\n            Self::New          => colours.new().paint(\"N\"),\n            Self::Modified     => colours.modified().paint(\"M\"),\n            Self::Deleted      => colours.deleted().paint(\"D\"),\n            Self::Renamed      => colours.renamed().paint(\"R\"),\n            Self::TypeChange   => colours.type_change().paint(\"T\"),\n            Self::Ignored      => colours.ignored().paint(\"I\"),\n            Self::Conflicted   => colours.conflicted().paint(\"U\"),\n        }\n    }\n}\n\n\npub trait Colours {\n    fn not_modified(&self) -> Style;\n    #[allow(clippy::new_ret_no_self)]\n    fn new(&self) -> Style;\n    fn modified(&self) -> Style;\n    fn deleted(&self) -> Style;\n    fn renamed(&self) -> Style;\n    fn type_change(&self) -> Style;\n    fn ignored(&self) -> Style;\n    fn conflicted(&self) -> Style;\n}\n\n\n#[cfg(test)]\npub mod test {\n    use super::Colours;\n    use crate::output::cell::{TextCell, DisplayWidth};\n    use crate::fs::fields as f;\n\n    use ansi_term::Colour::*;\n    use ansi_term::Style;\n\n\n    struct TestColours;\n\n    impl Colours for TestColours {\n        fn not_modified(&self) -> Style { Fixed(90).normal() }\n        fn new(&self)          -> Style { Fixed(91).normal() }\n        fn modified(&self)     -> Style { Fixed(92).normal() }\n        fn deleted(&self)      -> Style { Fixed(93).normal() }\n        fn renamed(&self)      -> Style { Fixed(94).normal() }\n        fn type_change(&self)  -> Style { Fixed(95).normal() }\n        fn ignored(&self)      -> Style { Fixed(96).normal() }\n        fn conflicted(&self)   -> Style { Fixed(97).normal() }\n    }\n\n\n    #[test]\n    fn git_blank() {\n        let stati = f::Git {\n            staged:   f::GitStatus::NotModified,\n            unstaged: f::GitStatus::NotModified,\n        };\n\n        let expected = TextCell {\n            width: DisplayWidth::from(2),\n            contents: vec![\n                Fixed(90).paint(\"-\"),\n                Fixed(90).paint(\"-\"),\n            ].into(),\n        };\n\n        assert_eq!(expected, stati.render(&TestColours))\n    }\n\n\n    #[test]\n    fn git_new_changed() {\n        let stati = f::Git {\n            staged:   f::GitStatus::New,\n            unstaged: f::GitStatus::Modified,\n        };\n\n        let expected = TextCell {\n            width: DisplayWidth::from(2),\n            contents: vec![\n                Fixed(91).paint(\"N\"),\n                Fixed(92).paint(\"M\"),\n            ].into(),\n        };\n\n        assert_eq!(expected, stati.render(&TestColours))\n    }\n}\n"
  },
  {
    "path": "src/output/render/groups.rs",
    "content": "use ansi_term::Style;\nuse users::{Users, Groups};\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\nuse crate::output::table::UserFormat;\n\n\nimpl f::Group {\n    pub fn render<C: Colours, U: Users+Groups>(self, colours: &C, users: &U, format: UserFormat) -> TextCell {\n        use users::os::unix::GroupExt;\n\n        let mut style = colours.not_yours();\n\n        let group = match users.get_group_by_gid(self.0) {\n            Some(g)  => (*g).clone(),\n            None     => return TextCell::paint(style, self.0.to_string()),\n        };\n\n        let current_uid = users.get_current_uid();\n        if let Some(current_user) = users.get_user_by_uid(current_uid) {\n\n            if current_user.primary_group_id() == group.gid()\n            || group.members().iter().any(|u| u == current_user.name())\n            {\n                style = colours.yours();\n            }\n        }\n\n        let group_name = match format {\n            UserFormat::Name => group.name().to_string_lossy().into(),\n            UserFormat::Numeric => group.gid().to_string(),\n        };\n\n        TextCell::paint(style, group_name)\n    }\n}\n\n\npub trait Colours {\n    fn yours(&self) -> Style;\n    fn not_yours(&self) -> Style;\n}\n\n\n#[cfg(test)]\n#[allow(unused_results)]\npub mod test {\n    use super::Colours;\n    use crate::fs::fields as f;\n    use crate::output::cell::TextCell;\n    use crate::output::table::UserFormat;\n\n    use users::{User, Group};\n    use users::mock::MockUsers;\n    use users::os::unix::GroupExt;\n    use ansi_term::Colour::*;\n    use ansi_term::Style;\n\n\n    struct TestColours;\n\n    impl Colours for TestColours {\n        fn yours(&self)     -> Style { Fixed(80).normal() }\n        fn not_yours(&self) -> Style { Fixed(81).normal() }\n    }\n\n\n    #[test]\n    fn named() {\n        let mut users = MockUsers::with_current_uid(1000);\n        users.add_group(Group::new(100, \"folk\"));\n\n        let group = f::Group(100);\n        let expected = TextCell::paint_str(Fixed(81).normal(), \"folk\");\n        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name));\n\n        let expected = TextCell::paint_str(Fixed(81).normal(), \"100\");\n        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric));\n    }\n\n\n    #[test]\n    fn unnamed() {\n        let users = MockUsers::with_current_uid(1000);\n\n        let group = f::Group(100);\n        let expected = TextCell::paint_str(Fixed(81).normal(), \"100\");\n        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name));\n        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric));\n    }\n\n    #[test]\n    fn primary() {\n        let mut users = MockUsers::with_current_uid(2);\n        users.add_user(User::new(2, \"eve\", 100));\n        users.add_group(Group::new(100, \"folk\"));\n\n        let group = f::Group(100);\n        let expected = TextCell::paint_str(Fixed(80).normal(), \"folk\");\n        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name))\n    }\n\n    #[test]\n    fn secondary() {\n        let mut users = MockUsers::with_current_uid(2);\n        users.add_user(User::new(2, \"eve\", 666));\n\n        let test_group = Group::new(100, \"folk\").add_member(\"eve\");\n        users.add_group(test_group);\n\n        let group = f::Group(100);\n        let expected = TextCell::paint_str(Fixed(80).normal(), \"folk\");\n        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name))\n    }\n\n    #[test]\n    fn overflow() {\n        let group = f::Group(2_147_483_648);\n        let expected = TextCell::paint_str(Fixed(81).normal(), \"2147483648\");\n        assert_eq!(expected, group.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));\n    }\n}\n"
  },
  {
    "path": "src/output/render/inode.rs",
    "content": "use ansi_term::Style;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\n\n\nimpl f::Inode {\n    pub fn render(self, style: Style) -> TextCell {\n        TextCell::paint(style, self.0.to_string())\n    }\n}\n\n\n#[cfg(test)]\npub mod test {\n    use crate::output::cell::TextCell;\n    use crate::fs::fields as f;\n\n    use ansi_term::Colour::*;\n\n\n    #[test]\n    fn blocklessness() {\n        let io = f::Inode(1_414_213);\n        let expected = TextCell::paint_str(Cyan.underline(), \"1414213\");\n        assert_eq!(expected, io.render(Cyan.underline()));\n    }\n}\n"
  },
  {
    "path": "src/output/render/links.rs",
    "content": "use ansi_term::Style;\nuse locale::Numeric as NumericLocale;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\n\n\nimpl f::Links {\n    pub fn render<C: Colours>(&self, colours: &C, numeric: &NumericLocale) -> TextCell {\n        let style = if self.multiple { colours.multi_link_file() }\n                                else { colours.normal() };\n\n        TextCell::paint(style, numeric.format_int(self.count))\n    }\n}\n\n\npub trait Colours {\n    fn normal(&self) -> Style;\n    fn multi_link_file(&self) -> Style;\n}\n\n\n#[cfg(test)]\npub mod test {\n    use super::Colours;\n    use crate::output::cell::{TextCell, DisplayWidth};\n    use crate::fs::fields as f;\n\n    use ansi_term::Colour::*;\n    use ansi_term::Style;\n    use locale;\n\n\n    struct TestColours;\n\n    impl Colours for TestColours {\n        fn normal(&self)           -> Style { Blue.normal() }\n        fn multi_link_file(&self)  -> Style { Blue.on(Red) }\n    }\n\n\n    #[test]\n    fn regular_file() {\n        let stati = f::Links {\n            count:    1,\n            multiple: false,\n        };\n\n        let expected = TextCell {\n            width: DisplayWidth::from(1),\n            contents: vec![ Blue.paint(\"1\") ].into(),\n        };\n\n        assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english()));\n    }\n\n    #[test]\n    fn regular_directory() {\n        let stati = f::Links {\n            count:    3005,\n            multiple: false,\n        };\n\n        let expected = TextCell {\n            width: DisplayWidth::from(5),\n            contents: vec![ Blue.paint(\"3,005\") ].into(),\n        };\n\n        assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english()));\n    }\n\n    #[test]\n    fn popular_file() {\n        let stati = f::Links {\n            count:    3005,\n            multiple: true,\n        };\n\n        let expected = TextCell {\n            width: DisplayWidth::from(5),\n            contents: vec![ Blue.on(Red).paint(\"3,005\") ].into(),\n        };\n\n        assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english()));\n    }\n}\n"
  },
  {
    "path": "src/output/render/mod.rs",
    "content": "mod blocks;\npub use self::blocks::Colours as BlocksColours;\n\nmod filetype;\npub use self::filetype::Colours as FiletypeColours;\n\nmod git;\npub use self::git::Colours as GitColours;\n\n#[cfg(unix)]\nmod groups;\n#[cfg(unix)]\npub use self::groups::Colours as GroupColours;\n\nmod inode;\n// inode uses just one colour\n\nmod links;\npub use self::links::Colours as LinksColours;\n\nmod permissions;\npub use self::permissions::Colours as PermissionsColours;\n\nmod size;\npub use self::size::Colours as SizeColours;\n\nmod times;\npub use self::times::Render as TimeRender;\n// times does too\n\n#[cfg(unix)]\nmod users;\n#[cfg(unix)]\npub use self::users::Colours as UserColours;\n\nmod octal;\n// octal uses just one colour\n"
  },
  {
    "path": "src/output/render/octal.rs",
    "content": "use ansi_term::Style;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\n\n\nimpl f::OctalPermissions {\n    fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 {\n        u8::from(r) * 4 + u8::from(w) * 2 + u8::from(x)\n    }\n\n    pub fn render(&self, style: Style) -> TextCell {\n        let perm = &self.permissions;\n        let octal_sticky = Self::bits_to_octal(perm.setuid, perm.setgid, perm.sticky);\n        let octal_owner  = Self::bits_to_octal(perm.user_read, perm.user_write, perm.user_execute);\n        let octal_group  = Self::bits_to_octal(perm.group_read, perm.group_write, perm.group_execute);\n        let octal_other  = Self::bits_to_octal(perm.other_read, perm.other_write, perm.other_execute);\n\n        TextCell::paint(style, format!(\"{}{}{}{}\", octal_sticky, octal_owner, octal_group, octal_other))\n    }\n}\n\n\n#[cfg(test)]\npub mod test {\n    use crate::output::cell::TextCell;\n    use crate::fs::fields as f;\n\n    use ansi_term::Colour::*;\n\n\n    #[test]\n    fn normal_folder() {\n        let bits = f::Permissions {\n            user_read:  true, user_write:  true,  user_execute:  true, setuid: false,\n            group_read: true, group_write: false, group_execute: true, setgid: false,\n            other_read: true, other_write: false, other_execute: true, sticky: false,\n        };\n\n        let octal = f::OctalPermissions{ permissions: bits };\n\n        let expected = TextCell::paint_str(Purple.bold(), \"0755\");\n        assert_eq!(expected, octal.render(Purple.bold()));\n    }\n\n    #[test]\n    fn normal_file() {\n        let bits = f::Permissions {\n            user_read:  true, user_write:  true,  user_execute:  false, setuid: false,\n            group_read: true, group_write: false, group_execute: false, setgid: false,\n            other_read: true, other_write: false, other_execute: false, sticky: false,\n        };\n\n        let octal = f::OctalPermissions{ permissions: bits };\n\n        let expected = TextCell::paint_str(Purple.bold(), \"0644\");\n        assert_eq!(expected, octal.render(Purple.bold()));\n    }\n\n    #[test]\n    fn secret_file() {\n        let bits = f::Permissions {\n            user_read:  true,  user_write:  true,  user_execute:  false, setuid: false,\n            group_read: false, group_write: false, group_execute: false, setgid: false,\n            other_read: false, other_write: false, other_execute: false, sticky: false,\n        };\n\n        let octal = f::OctalPermissions{ permissions: bits };\n\n        let expected = TextCell::paint_str(Purple.bold(), \"0600\");\n        assert_eq!(expected, octal.render(Purple.bold()));\n    }\n\n    #[test]\n    fn sticky1() {\n        let bits = f::Permissions {\n            user_read:  true, user_write:  true,  user_execute:  true, setuid: true,\n            group_read: true, group_write: true,  group_execute: true, setgid: false,\n            other_read: true, other_write: true,  other_execute: true, sticky: false,\n        };\n\n        let octal = f::OctalPermissions{ permissions: bits };\n\n        let expected = TextCell::paint_str(Purple.bold(), \"4777\");\n        assert_eq!(expected, octal.render(Purple.bold()));\n\n    }\n\n    #[test]\n    fn sticky2() {\n        let bits = f::Permissions {\n            user_read:  true, user_write:  true,  user_execute:  true, setuid: false,\n            group_read: true, group_write: true,  group_execute: true, setgid: true,\n            other_read: true, other_write: true,  other_execute: true, sticky: false,\n        };\n\n        let octal = f::OctalPermissions{ permissions: bits };\n\n        let expected = TextCell::paint_str(Purple.bold(), \"2777\");\n        assert_eq!(expected, octal.render(Purple.bold()));\n    }\n\n    #[test]\n    fn sticky3() {\n        let bits = f::Permissions {\n            user_read:  true, user_write:  true,  user_execute:  true, setuid: false,\n            group_read: true, group_write: true,  group_execute: true, setgid: false,\n            other_read: true, other_write: true,  other_execute: true, sticky: true,\n        };\n\n        let octal = f::OctalPermissions{ permissions: bits };\n\n        let expected = TextCell::paint_str(Purple.bold(), \"1777\");\n        assert_eq!(expected, octal.render(Purple.bold()));\n    }\n}\n"
  },
  {
    "path": "src/output/render/permissions.rs",
    "content": "use ansi_term::{ANSIString, Style};\n\nuse crate::fs::fields as f;\nuse crate::output::cell::{TextCell, DisplayWidth};\nuse crate::output::render::FiletypeColours;\n\n\nimpl f::PermissionsPlus {\n    #[cfg(unix)]\n    pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {\n        let mut chars = vec![ self.file_type.render(colours) ];\n        chars.extend(self.permissions.render(colours, self.file_type.is_regular_file()));\n\n        if self.xattrs {\n           chars.push(colours.attribute().paint(\"@\"));\n        }\n\n        // As these are all ASCII characters, we can guarantee that they’re\n        // all going to be one character wide, and don’t need to compute the\n        // cell’s display width.\n        TextCell {\n            width:    DisplayWidth::from(chars.len()),\n            contents: chars.into(),\n        }\n    }\n\n    #[cfg(windows)]\n    pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {\n        let mut chars = vec![ self.attributes.render_type(colours) ];\n        chars.extend(self.attributes.render(colours));\n\n        TextCell {\n            width:    DisplayWidth::from(chars.len()),\n            contents: chars.into(),\n        }\n    }\n}\n\n\nimpl f::Permissions {\n    pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {\n\n        let bit = |bit, chr: &'static str, style: Style| {\n            if bit { style.paint(chr) }\n              else { colours.dash().paint(\"-\") }\n        };\n\n        vec![\n            bit(self.user_read,   \"r\", colours.user_read()),\n            bit(self.user_write,  \"w\", colours.user_write()),\n            self.user_execute_bit(colours, is_regular_file),\n            bit(self.group_read,  \"r\", colours.group_read()),\n            bit(self.group_write, \"w\", colours.group_write()),\n            self.group_execute_bit(colours),\n            bit(self.other_read,  \"r\", colours.other_read()),\n            bit(self.other_write, \"w\", colours.other_write()),\n            self.other_execute_bit(colours)\n        ]\n    }\n\n    fn user_execute_bit<C: Colours>(&self, colours: &C, is_regular_file: bool) -> ANSIString<'static> {\n        match (self.user_execute, self.setuid, is_regular_file) {\n            (false, false, _)      => colours.dash().paint(\"-\"),\n            (true,  false, false)  => colours.user_execute_other().paint(\"x\"),\n            (true,  false, true)   => colours.user_execute_file().paint(\"x\"),\n            (false, true,  _)      => colours.special_other().paint(\"S\"),\n            (true,  true,  false)  => colours.special_other().paint(\"s\"),\n            (true,  true,  true)   => colours.special_user_file().paint(\"s\"),\n        }\n    }\n\n    fn group_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {\n        match (self.group_execute, self.setgid) {\n            (false, false)  => colours.dash().paint(\"-\"),\n            (true,  false)  => colours.group_execute().paint(\"x\"),\n            (false, true)   => colours.special_other().paint(\"S\"),\n            (true,  true)   => colours.special_other().paint(\"s\"),\n        }\n    }\n\n    fn other_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {\n        match (self.other_execute, self.sticky) {\n            (false, false)  => colours.dash().paint(\"-\"),\n            (true,  false)  => colours.other_execute().paint(\"x\"),\n            (false, true)   => colours.special_other().paint(\"T\"),\n            (true,  true)   => colours.special_other().paint(\"t\"),\n        }\n    }\n}\n\nimpl f::Attributes {\n    pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> Vec<ANSIString<'static>> {\n        let bit = |bit, chr: &'static str, style: Style| {\n            if bit { style.paint(chr) }\n              else { colours.dash().paint(\"-\") }\n        };\n\n        vec![\n            bit(self.archive,   \"a\", colours.normal()),\n            bit(self.readonly,  \"r\", colours.user_read()),\n            bit(self.hidden,    \"h\", colours.special_user_file()),\n            bit(self.system,    \"s\", colours.special_other()),\n        ]\n    }\n\n    pub fn render_type<C: Colours+FiletypeColours>(&self, colours: &C) -> ANSIString<'static> {\n        if self.reparse_point {\n            return colours.pipe().paint(\"l\")\n        }\n        else if self.directory {\n            return colours.directory().paint(\"d\")\n        }\n        else {\n            return colours.dash().paint(\"-\")\n        }\n    }\n}\n\npub trait Colours {\n    fn dash(&self) -> Style;\n\n    fn user_read(&self) -> Style;\n    fn user_write(&self) -> Style;\n    fn user_execute_file(&self) -> Style;\n    fn user_execute_other(&self) -> Style;\n\n    fn group_read(&self) -> Style;\n    fn group_write(&self) -> Style;\n    fn group_execute(&self) -> Style;\n\n    fn other_read(&self) -> Style;\n    fn other_write(&self) -> Style;\n    fn other_execute(&self) -> Style;\n\n    fn special_user_file(&self) -> Style;\n    fn special_other(&self) -> Style;\n\n    fn attribute(&self) -> Style;\n}\n\n\n#[cfg(test)]\n#[allow(unused_results)]\npub mod test {\n    use super::Colours;\n    use crate::output::cell::TextCellContents;\n    use crate::fs::fields as f;\n\n    use ansi_term::Colour::*;\n    use ansi_term::Style;\n\n\n    struct TestColours;\n\n    impl Colours for TestColours {\n        fn dash(&self)                -> Style { Fixed(11).normal() }\n        fn user_read(&self)           -> Style { Fixed(101).normal() }\n        fn user_write(&self)          -> Style { Fixed(102).normal() }\n        fn user_execute_file(&self)   -> Style { Fixed(103).normal() }\n        fn user_execute_other(&self)  -> Style { Fixed(113).normal() }\n        fn group_read(&self)          -> Style { Fixed(104).normal() }\n        fn group_write(&self)         -> Style { Fixed(105).normal() }\n        fn group_execute(&self)       -> Style { Fixed(106).normal() }\n        fn other_read(&self)          -> Style { Fixed(107).normal() }\n        fn other_write(&self)         -> Style { Fixed(108).normal() }\n        fn other_execute(&self)       -> Style { Fixed(109).normal() }\n        fn special_user_file(&self)   -> Style { Fixed(110).normal() }\n        fn special_other(&self)       -> Style { Fixed(111).normal() }\n        fn attribute(&self)           -> Style { Fixed(112).normal() }\n    }\n\n\n    #[test]\n    fn negate() {\n        let bits = f::Permissions {\n            user_read:  false,  user_write:  false,  user_execute:  false,  setuid: false,\n            group_read: false,  group_write: false,  group_execute: false,  setgid: false,\n            other_read: false,  other_write: false,  other_execute: false,  sticky: false,\n        };\n\n        let expected = TextCellContents::from(vec![\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),\n        ]);\n\n        assert_eq!(expected, bits.render(&TestColours, false).into())\n    }\n\n\n    #[test]\n    fn affirm() {\n        let bits = f::Permissions {\n            user_read:  true,  user_write:  true,  user_execute:  true,  setuid: false,\n            group_read: true,  group_write: true,  group_execute: true,  setgid: false,\n            other_read: true,  other_write: true,  other_execute: true,  sticky: false,\n        };\n\n        let expected = TextCellContents::from(vec![\n            Fixed(101).paint(\"r\"),  Fixed(102).paint(\"w\"),  Fixed(103).paint(\"x\"),\n            Fixed(104).paint(\"r\"),  Fixed(105).paint(\"w\"),  Fixed(106).paint(\"x\"),\n            Fixed(107).paint(\"r\"),  Fixed(108).paint(\"w\"),  Fixed(109).paint(\"x\"),\n        ]);\n\n        assert_eq!(expected, bits.render(&TestColours, true).into())\n    }\n\n\n    #[test]\n    fn specials() {\n        let bits = f::Permissions {\n            user_read:  false,  user_write:  false,  user_execute:  true,  setuid: true,\n            group_read: false,  group_write: false,  group_execute: true,  setgid: true,\n            other_read: false,  other_write: false,  other_execute: true,  sticky: true,\n        };\n\n        let expected = TextCellContents::from(vec![\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(110).paint(\"s\"),\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(111).paint(\"s\"),\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(111).paint(\"t\"),\n        ]);\n\n        assert_eq!(expected, bits.render(&TestColours, true).into())\n    }\n\n\n    #[test]\n    fn extra_specials() {\n        let bits = f::Permissions {\n            user_read:  false,  user_write:  false,  user_execute:  false,  setuid: true,\n            group_read: false,  group_write: false,  group_execute: false,  setgid: true,\n            other_read: false,  other_write: false,  other_execute: false,  sticky: true,\n        };\n\n        let expected = TextCellContents::from(vec![\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(111).paint(\"S\"),\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(111).paint(\"S\"),\n            Fixed(11).paint(\"-\"),  Fixed(11).paint(\"-\"),  Fixed(111).paint(\"T\"),\n        ]);\n\n        assert_eq!(expected, bits.render(&TestColours, true).into())\n    }\n}\n"
  },
  {
    "path": "src/output/render/size.rs",
    "content": "use ansi_term::Style;\nuse locale::Numeric as NumericLocale;\nuse number_prefix::Prefix;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::{TextCell, DisplayWidth};\nuse crate::output::table::SizeFormat;\n\n\nimpl f::Size {\n    pub fn render<C: Colours>(self, colours: &C, size_format: SizeFormat, numerics: &NumericLocale) -> TextCell {\n        use number_prefix::NumberPrefix;\n\n        let size = match self {\n            Self::Some(s)             => s,\n            Self::None                => return TextCell::blank(colours.no_size()),\n            Self::DeviceIDs(ref ids)  => return ids.render(colours),\n        };\n\n        let result = match size_format {\n            SizeFormat::DecimalBytes  => NumberPrefix::decimal(size as f64),\n            SizeFormat::BinaryBytes   => NumberPrefix::binary(size as f64),\n            SizeFormat::JustBytes     => {\n\n                // Use the binary prefix to select a style.\n                let prefix = match NumberPrefix::binary(size as f64) {\n                    NumberPrefix::Standalone(_)   => None,\n                    NumberPrefix::Prefixed(p, _)  => Some(p),\n                };\n\n                // But format the number directly using the locale.\n                let string = numerics.format_int(size);\n\n                return TextCell::paint(colours.size(prefix), string);\n            }\n        };\n\n        let (prefix, n) = match result {\n            NumberPrefix::Standalone(b)   => return TextCell::paint(colours.size(None), numerics.format_int(b)),\n            NumberPrefix::Prefixed(p, n)  => (p, n),\n        };\n\n        let symbol = prefix.symbol();\n        let number = if n < 10_f64 {\n            numerics.format_float(n, 1)\n        } else {\n            numerics.format_int(n.round() as isize)\n        };\n\n        TextCell {\n            // symbol is guaranteed to be ASCII since unit prefixes are hardcoded.\n            width: DisplayWidth::from(&*number) + symbol.len(),\n            contents: vec![\n                colours.size(Some(prefix)).paint(number),\n                colours.unit(Some(prefix)).paint(symbol),\n            ].into(),\n        }\n    }\n}\n\n\nimpl f::DeviceIDs {\n    fn render<C: Colours>(self, colours: &C) -> TextCell {\n        let major = self.major.to_string();\n        let minor = self.minor.to_string();\n\n        TextCell {\n            width: DisplayWidth::from(major.len() + 1 + minor.len()),\n            contents: vec![\n                colours.major().paint(major),\n                colours.comma().paint(\",\"),\n                colours.minor().paint(minor),\n            ].into(),\n        }\n    }\n}\n\n\npub trait Colours {\n    fn size(&self, prefix: Option<Prefix>) -> Style;\n    fn unit(&self, prefix: Option<Prefix>) -> Style;\n    fn no_size(&self) -> Style;\n\n    fn major(&self) -> Style;\n    fn comma(&self) -> Style;\n    fn minor(&self) -> Style;\n}\n\n\n#[cfg(test)]\npub mod test {\n    use super::Colours;\n    use crate::output::cell::{TextCell, DisplayWidth};\n    use crate::output::table::SizeFormat;\n    use crate::fs::fields as f;\n\n    use locale::Numeric as NumericLocale;\n    use ansi_term::Colour::*;\n    use ansi_term::Style;\n    use number_prefix::Prefix;\n\n\n    struct TestColours;\n\n    impl Colours for TestColours {\n        fn size(&self, _prefix: Option<Prefix>) -> Style { Fixed(66).normal() }\n        fn unit(&self, _prefix: Option<Prefix>) -> Style { Fixed(77).bold() }\n        fn no_size(&self)          -> Style { Black.italic() }\n\n        fn major(&self) -> Style { Blue.on(Red) }\n        fn comma(&self) -> Style { Green.italic() }\n        fn minor(&self) -> Style { Cyan.on(Yellow) }\n    }\n\n\n    #[test]\n    fn directory() {\n        let directory = f::Size::None;\n        let expected = TextCell::blank(Black.italic());\n        assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english()))\n    }\n\n\n    #[test]\n    fn file_decimal() {\n        let directory = f::Size::Some(2_100_000);\n        let expected = TextCell {\n            width: DisplayWidth::from(4),\n            contents: vec![\n                Fixed(66).paint(\"2.1\"),\n                Fixed(77).bold().paint(\"M\"),\n            ].into(),\n        };\n\n        assert_eq!(expected, directory.render(&TestColours, SizeFormat::DecimalBytes, &NumericLocale::english()))\n    }\n\n\n    #[test]\n    fn file_binary() {\n        let directory = f::Size::Some(1_048_576);\n        let expected = TextCell {\n            width: DisplayWidth::from(5),\n            contents: vec![\n                Fixed(66).paint(\"1.0\"),\n                Fixed(77).bold().paint(\"Mi\"),\n            ].into(),\n        };\n\n        assert_eq!(expected, directory.render(&TestColours, SizeFormat::BinaryBytes, &NumericLocale::english()))\n    }\n\n\n    #[test]\n    fn file_bytes() {\n        let directory = f::Size::Some(1_048_576);\n        let expected = TextCell {\n            width: DisplayWidth::from(9),\n            contents: vec![\n                Fixed(66).paint(\"1,048,576\"),\n            ].into(),\n        };\n\n        assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english()))\n    }\n\n\n    #[test]\n    fn device_ids() {\n        let directory = f::Size::DeviceIDs(f::DeviceIDs { major: 10, minor: 80 });\n        let expected = TextCell {\n            width: DisplayWidth::from(5),\n            contents: vec![\n                Blue.on(Red).paint(\"10\"),\n                Green.italic().paint(\",\"),\n                Cyan.on(Yellow).paint(\"80\"),\n            ].into(),\n        };\n\n        assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english()))\n    }\n}\n"
  },
  {
    "path": "src/output/render/times.rs",
    "content": "use std::time::SystemTime;\n\nuse datetime::TimeZone;\nuse ansi_term::Style;\n\nuse crate::output::cell::TextCell;\nuse crate::output::time::TimeFormat;\n\n\npub trait Render {\n    fn render(self, style: Style, tz: &Option<TimeZone>, format: TimeFormat) -> TextCell;\n}\n\nimpl Render for Option<SystemTime> {\n    fn render(self, style: Style, tz: &Option<TimeZone>, format: TimeFormat) -> TextCell {\n        let datestamp = if let Some(time) = self {\n            if let Some(ref tz) = tz {\n                format.format_zoned(time, tz)\n            }\n            else {\n                format.format_local(time)\n            }\n        }\n        else {\n            String::from(\"-\")\n        };\n\n        TextCell::paint(style, datestamp)\n    }\n}\n"
  },
  {
    "path": "src/output/render/users.rs",
    "content": "use ansi_term::Style;\nuse users::Users;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\nuse crate::output::table::UserFormat;\n\n\nimpl f::User {\n    pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U, format: UserFormat) -> TextCell {\n        let user_name = match (format, users.get_user_by_uid(self.0)) {\n            (_, None)                      => self.0.to_string(),\n            (UserFormat::Numeric, _)       => self.0.to_string(),\n            (UserFormat::Name, Some(user)) => user.name().to_string_lossy().into(),\n        };\n\n        let style = if users.get_current_uid() == self.0 { colours.you() }\n                                                    else { colours.someone_else() };\n        TextCell::paint(style, user_name)\n    }\n}\n\n\npub trait Colours {\n    fn you(&self) -> Style;\n    fn someone_else(&self) -> Style;\n}\n\n\n#[cfg(test)]\n#[allow(unused_results)]\npub mod test {\n    use super::Colours;\n    use crate::fs::fields as f;\n    use crate::output::cell::TextCell;\n    use crate::output::table::UserFormat;\n\n    use users::User;\n    use users::mock::MockUsers;\n    use ansi_term::Colour::*;\n    use ansi_term::Style;\n\n\n    struct TestColours;\n\n    impl Colours for TestColours {\n        fn you(&self)          -> Style { Red.bold() }\n        fn someone_else(&self) -> Style { Blue.underline() }\n    }\n\n\n    #[test]\n    fn named() {\n        let mut users = MockUsers::with_current_uid(1000);\n        users.add_user(User::new(1000, \"enoch\", 100));\n\n        let user = f::User(1000);\n        let expected = TextCell::paint_str(Red.bold(), \"enoch\");\n        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));\n\n        let expected = TextCell::paint_str(Red.bold(), \"1000\");\n        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric));\n    }\n\n    #[test]\n    fn unnamed() {\n        let users = MockUsers::with_current_uid(1000);\n\n        let user = f::User(1000);\n        let expected = TextCell::paint_str(Red.bold(), \"1000\");\n        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));\n        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric));\n    }\n\n    #[test]\n    fn different_named() {\n        let mut users = MockUsers::with_current_uid(0);\n        users.add_user(User::new(1000, \"enoch\", 100));\n\n        let user = f::User(1000);\n        let expected = TextCell::paint_str(Blue.underline(), \"enoch\");\n        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));\n    }\n\n    #[test]\n    fn different_unnamed() {\n        let user = f::User(1000);\n        let expected = TextCell::paint_str(Blue.underline(), \"1000\");\n        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));\n    }\n\n    #[test]\n    fn overflow() {\n        let user = f::User(2_147_483_648);\n        let expected = TextCell::paint_str(Blue.underline(), \"2147483648\");\n        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));\n    }\n}\n"
  },
  {
    "path": "src/output/table.rs",
    "content": "use std::cmp::max;\nuse std::env;\nuse std::ops::Deref;\n#[cfg(unix)]\nuse std::sync::{Mutex, MutexGuard};\n\nuse datetime::TimeZone;\nuse zoneinfo_compiled::{CompiledData, Result as TZResult};\n\nuse lazy_static::lazy_static;\nuse log::*;\n#[cfg(unix)]\nuse users::UsersCache;\n\nuse crate::fs::{File, fields as f};\nuse crate::fs::feature::git::GitCache;\nuse crate::output::cell::TextCell;\nuse crate::output::render::TimeRender;\nuse crate::output::time::TimeFormat;\nuse crate::theme::Theme;\n\n\n/// Options for displaying a table.\n#[derive(PartialEq, Eq, Debug)]\npub struct Options {\n    pub size_format: SizeFormat,\n    pub time_format: TimeFormat,\n    pub user_format: UserFormat,\n    pub columns: Columns,\n}\n\n/// Extra columns to display in the table.\n#[allow(clippy::struct_excessive_bools)]\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub struct Columns {\n\n    /// At least one of these timestamps will be shown.\n    pub time_types: TimeTypes,\n\n    // The rest are just on/off\n    pub inode: bool,\n    pub links: bool,\n    pub blocks: bool,\n    pub group: bool,\n    pub git: bool,\n    pub octal: bool,\n\n    // Defaults to true:\n    pub permissions: bool,\n    pub filesize: bool,\n    pub user: bool,\n}\n\nimpl Columns {\n    pub fn collect(&self, actually_enable_git: bool) -> Vec<Column> {\n        let mut columns = Vec::with_capacity(4);\n\n        if self.inode {\n            #[cfg(unix)]\n            columns.push(Column::Inode);\n        }\n\n        if self.octal {\n            #[cfg(unix)]\n            columns.push(Column::Octal);\n        }\n\n        if self.permissions {\n            columns.push(Column::Permissions);\n        }\n\n        if self.links {\n            #[cfg(unix)]\n            columns.push(Column::HardLinks);\n        }\n\n        if self.filesize {\n            columns.push(Column::FileSize);\n        }\n\n        if self.blocks {\n            #[cfg(unix)]\n            columns.push(Column::Blocks);\n        }\n\n        if self.user {\n            #[cfg(unix)]\n            columns.push(Column::User);\n        }\n\n        if self.group {\n            #[cfg(unix)]\n            columns.push(Column::Group);\n        }\n\n        if self.time_types.modified {\n            columns.push(Column::Timestamp(TimeType::Modified));\n        }\n\n        if self.time_types.changed {\n            columns.push(Column::Timestamp(TimeType::Changed));\n        }\n\n        if self.time_types.created {\n            columns.push(Column::Timestamp(TimeType::Created));\n        }\n\n        if self.time_types.accessed {\n            columns.push(Column::Timestamp(TimeType::Accessed));\n        }\n\n        if self.git && actually_enable_git {\n            columns.push(Column::GitStatus);\n        }\n\n        columns\n    }\n}\n\n\n/// A table contains these.\n#[derive(Debug, Copy, Clone)]\npub enum Column {\n    Permissions,\n    FileSize,\n    Timestamp(TimeType),\n    #[cfg(unix)]\n    Blocks,\n    #[cfg(unix)]\n    User,\n    #[cfg(unix)]\n    Group,\n    #[cfg(unix)]\n    HardLinks,\n    #[cfg(unix)]\n    Inode,\n    GitStatus,\n    #[cfg(unix)]\n    Octal,\n}\n\n/// Each column can pick its own **Alignment**. Usually, numbers are\n/// right-aligned, and text is left-aligned.\n#[derive(Copy, Clone)]\npub enum Alignment {\n    Left,\n    Right,\n}\n\nimpl Column {\n\n    /// Get the alignment this column should use.\n    #[cfg(unix)]\n    pub fn alignment(self) -> Alignment {\n        match self {\n            Self::FileSize   |\n            Self::HardLinks  |\n            Self::Inode      |\n            Self::Blocks     |\n            Self::GitStatus  => Alignment::Right,\n            _                => Alignment::Left,\n        }\n    }\n\n    #[cfg(windows)]\n    pub fn alignment(&self) -> Alignment {\n        match self {\n            Self::FileSize   |\n            Self::GitStatus  => Alignment::Right,\n            _                => Alignment::Left,\n        }\n    }\n\n    /// Get the text that should be printed at the top, when the user elects\n    /// to have a header row printed.\n    pub fn header(self) -> &'static str {\n        match self {\n            #[cfg(unix)]\n            Self::Permissions   => \"Permissions\",\n            #[cfg(windows)]\n            Self::Permissions   => \"Mode\",\n            Self::FileSize      => \"Size\",\n            Self::Timestamp(t)  => t.header(),\n            #[cfg(unix)]\n            Self::Blocks        => \"Blocks\",\n            #[cfg(unix)]\n            Self::User          => \"User\",\n            #[cfg(unix)]\n            Self::Group         => \"Group\",\n            #[cfg(unix)]\n            Self::HardLinks     => \"Links\",\n            #[cfg(unix)]\n            Self::Inode         => \"inode\",\n            Self::GitStatus     => \"Git\",\n            #[cfg(unix)]\n            Self::Octal         => \"Octal\",\n        }\n    }\n}\n\n\n/// Formatting options for file sizes.\n#[allow(clippy::enum_variant_names)]\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum SizeFormat {\n\n    /// Format the file size using **decimal** prefixes, such as “kilo”,\n    /// “mega”, or “giga”.\n    DecimalBytes,\n\n    /// Format the file size using **binary** prefixes, such as “kibi”,\n    /// “mebi”, or “gibi”.\n    BinaryBytes,\n\n    /// Do no formatting and just display the size as a number of bytes.\n    JustBytes,\n}\n\n/// Formatting options for user and group.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum UserFormat {\n    /// The UID / GID\n    Numeric,\n    /// Show the name\n    Name,\n}\n\nimpl Default for SizeFormat {\n    fn default() -> Self {\n        Self::DecimalBytes\n    }\n}\n\n\n/// The types of a file’s time fields. These three fields are standard\n/// across most (all?) operating systems.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum TimeType {\n\n    /// The file’s modified time (`st_mtime`).\n    Modified,\n\n    /// The file’s changed time (`st_ctime`)\n    Changed,\n\n    /// The file’s accessed time (`st_atime`).\n    Accessed,\n\n    /// The file’s creation time (`btime` or `birthtime`).\n    Created,\n}\n\nimpl TimeType {\n\n    /// Returns the text to use for a column’s heading in the columns output.\n    pub fn header(self) -> &'static str {\n        match self {\n            Self::Modified  => \"Date Modified\",\n            Self::Changed   => \"Date Changed\",\n            Self::Accessed  => \"Date Accessed\",\n            Self::Created   => \"Date Created\",\n        }\n    }\n}\n\n\n/// Fields for which of a file’s time fields should be displayed in the\n/// columns output.\n///\n/// There should always be at least one of these — there’s no way to disable\n/// the time columns entirely (yet).\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\n#[allow(clippy::struct_excessive_bools)]\npub struct TimeTypes {\n    pub modified: bool,\n    pub changed:  bool,\n    pub accessed: bool,\n    pub created:  bool,\n}\n\nimpl Default for TimeTypes {\n\n    /// By default, display just the ‘modified’ time. This is the most\n    /// common option, which is why it has this shorthand.\n    fn default() -> Self {\n        Self {\n            modified: true,\n            changed:  false,\n            accessed: false,\n            created:  false,\n        }\n    }\n}\n\n\n/// The **environment** struct contains any data that could change between\n/// running instances of exa, depending on the user’s computer’s configuration.\n///\n/// Any environment field should be able to be mocked up for test runs.\npub struct Environment {\n\n    /// Localisation rules for formatting numbers.\n    numeric: locale::Numeric,\n\n    /// The computer’s current time zone. This gets used to determine how to\n    /// offset files’ timestamps.\n    tz: Option<TimeZone>,\n\n    /// Mapping cache of user IDs to usernames.\n    #[cfg(unix)]\n    users: Mutex<UsersCache>,\n}\n\nimpl Environment {\n    #[cfg(unix)]\n    pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> {\n        self.users.lock().unwrap()\n    }\n\n    fn load_all() -> Self {\n        let tz = match determine_time_zone() {\n            Ok(t) => {\n                Some(t)\n            }\n            Err(ref e) => {\n                println!(\"Unable to determine time zone: {}\", e);\n                None\n            }\n        };\n\n        let numeric = locale::Numeric::load_user_locale()\n                             .unwrap_or_else(|_| locale::Numeric::english());\n\n        #[cfg(unix)]\n        let users = Mutex::new(UsersCache::new());\n\n        Self { numeric, tz, #[cfg(unix)] users }\n    }\n}\n\n#[cfg(unix)]\nfn determine_time_zone() -> TZResult<TimeZone> {\n    if let Ok(file) = env::var(\"TZ\") {\n        TimeZone::from_file({\n            if file.starts_with('/') {\n                file\n            } else {\n                format!(\"/usr/share/zoneinfo/{}\", {\n                    if file.starts_with(':') {\n                        file.replacen(':', \"\", 1)\n                    } else {\n                        file\n                    }\n                })\n            }\n        })\n    } else {\n        TimeZone::from_file(\"/etc/localtime\")\n    }\n}\n\n#[cfg(windows)]\nfn determine_time_zone() -> TZResult<TimeZone> {\n    use datetime::zone::{FixedTimespan, FixedTimespanSet, StaticTimeZone, TimeZoneSource};\n    use std::borrow::Cow;\n\n    Ok(TimeZone(TimeZoneSource::Static(&StaticTimeZone {\n        name: \"Unsupported\",\n        fixed_timespans: FixedTimespanSet {\n            first: FixedTimespan {\n                offset: 0,\n                is_dst: false,\n                name: Cow::Borrowed(\"ZONE_A\"),\n            },\n            rest: &[(\n                1206838800,\n                FixedTimespan {\n                    offset: 3600,\n                    is_dst: false,\n                    name: Cow::Borrowed(\"ZONE_B\"),\n                },\n            )],\n        },\n    })))\n}\n\nlazy_static! {\n    static ref ENVIRONMENT: Environment = Environment::load_all();\n}\n\n\npub struct Table<'a> {\n    columns: Vec<Column>,\n    theme: &'a Theme,\n    env: &'a Environment,\n    widths: TableWidths,\n    time_format: TimeFormat,\n    size_format: SizeFormat,\n    user_format: UserFormat,\n    git: Option<&'a GitCache>,\n}\n\n#[derive(Clone)]\npub struct Row {\n    cells: Vec<TextCell>,\n}\n\nimpl<'a, 'f> Table<'a> {\n    pub fn new(options: &'a Options, git: Option<&'a GitCache>, theme: &'a Theme) -> Table<'a> {\n        let columns = options.columns.collect(git.is_some());\n        let widths = TableWidths::zero(columns.len());\n        let env = &*ENVIRONMENT;\n\n        Table {\n            theme,\n            widths,\n            columns,\n            git,\n            env,\n            time_format: options.time_format,\n            size_format: options.size_format,\n            user_format: options.user_format,\n        }\n    }\n\n    pub fn widths(&self) -> &TableWidths {\n        &self.widths\n    }\n\n    pub fn header_row(&self) -> Row {\n        let cells = self.columns.iter()\n                        .map(|c| TextCell::paint_str(self.theme.ui.header, c.header()))\n                        .collect();\n\n        Row { cells }\n    }\n\n    pub fn row_for_file(&self, file: &File<'_>, xattrs: bool) -> Row {\n        let cells = self.columns.iter()\n                        .map(|c| self.display(file, *c, xattrs))\n                        .collect();\n\n        Row { cells }\n    }\n\n    pub fn add_widths(&mut self, row: &Row) {\n        self.widths.add_widths(row)\n    }\n\n    fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> f::PermissionsPlus {\n        f::PermissionsPlus {\n            file_type: file.type_char(),\n            #[cfg(unix)]\n            permissions: file.permissions(),\n            #[cfg(windows)]\n            attributes: file.attributes(),\n            xattrs,\n        }\n    }\n\n    #[cfg(unix)]\n    fn octal_permissions(&self, file: &File<'_>) -> f::OctalPermissions {\n        f::OctalPermissions {\n            permissions: file.permissions(),\n        }\n    }\n\n    fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell {\n        match column {\n            Column::Permissions => {\n                self.permissions_plus(file, xattrs).render(self.theme)\n            }\n            Column::FileSize => {\n                file.size().render(self.theme, self.size_format, &self.env.numeric)\n            }\n            #[cfg(unix)]\n            Column::HardLinks => {\n                file.links().render(self.theme, &self.env.numeric)\n            }\n            #[cfg(unix)]\n            Column::Inode => {\n                file.inode().render(self.theme.ui.inode)\n            }\n            #[cfg(unix)]\n            Column::Blocks => {\n                file.blocks().render(self.theme)\n            }\n            #[cfg(unix)]\n            Column::User => {\n                file.user().render(self.theme, &*self.env.lock_users(), self.user_format)\n            }\n            #[cfg(unix)]\n            Column::Group => {\n                file.group().render(self.theme, &*self.env.lock_users(), self.user_format)\n            }\n            Column::GitStatus => {\n                self.git_status(file).render(self.theme)\n            }\n            #[cfg(unix)]\n            Column::Octal => {\n                self.octal_permissions(file).render(self.theme.ui.octal)\n            }\n\n            Column::Timestamp(TimeType::Modified)  => {\n                file.modified_time().render(self.theme.ui.date, &self.env.tz, self.time_format)\n            }\n            Column::Timestamp(TimeType::Changed)   => {\n                file.changed_time().render(self.theme.ui.date, &self.env.tz, self.time_format)\n            }\n            Column::Timestamp(TimeType::Created)   => {\n                file.created_time().render(self.theme.ui.date, &self.env.tz, self.time_format)\n            }\n            Column::Timestamp(TimeType::Accessed)  => {\n                file.accessed_time().render(self.theme.ui.date, &self.env.tz, self.time_format)\n            }\n        }\n    }\n\n    fn git_status(&self, file: &File<'_>) -> f::Git {\n        debug!(\"Getting Git status for file {:?}\", file.path);\n\n        self.git\n            .map(|g| g.get(&file.path, file.is_directory()))\n            .unwrap_or_default()\n    }\n\n    pub fn render(&self, row: Row) -> TextCell {\n        let mut cell = TextCell::default();\n\n        let iter = row.cells.into_iter()\n                      .zip(self.widths.iter())\n                      .enumerate();\n\n        for (n, (this_cell, width)) in iter {\n            let padding = width - *this_cell.width;\n\n            match self.columns[n].alignment() {\n                Alignment::Left => {\n                    cell.append(this_cell);\n                    cell.add_spaces(padding);\n                }\n                Alignment::Right => {\n                    cell.add_spaces(padding);\n                    cell.append(this_cell);\n                }\n            }\n\n            cell.add_spaces(1);\n        }\n\n        cell\n    }\n}\n\n\npub struct TableWidths(Vec<usize>);\n\nimpl Deref for TableWidths {\n    type Target = [usize];\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl TableWidths {\n    pub fn zero(count: usize) -> Self {\n        Self(vec![0; count])\n    }\n\n    pub fn add_widths(&mut self, row: &Row) {\n        for (old_width, cell) in self.0.iter_mut().zip(row.cells.iter()) {\n            *old_width = max(*old_width, *cell.width);\n        }\n    }\n\n    pub fn total(&self) -> usize {\n        self.0.len() + self.0.iter().sum::<usize>()\n    }\n}\n"
  },
  {
    "path": "src/output/time.rs",
    "content": "//! Timestamp formatting.\n\nuse std::time::{SystemTime, UNIX_EPOCH};\n\nuse datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece};\nuse datetime::fmt::DateFormat;\n\nuse lazy_static::lazy_static;\nuse unicode_width::UnicodeWidthStr;\n\n\n/// Every timestamp in exa needs to be rendered by a **time format**.\n/// Formatting times is tricky, because how a timestamp is rendered can\n/// depend on one or more of the following:\n///\n/// - The user’s locale, for printing the month name as “Feb”, or as “fév”,\n///   or as “2月”;\n/// - The current year, because certain formats will be less precise when\n///   dealing with dates far in the past;\n/// - The formatting style that the user asked for on the command-line.\n///\n/// Because not all formatting styles need the same data, they all have their\n/// own enum variants. It’s not worth looking the locale up if the formatter\n/// prints month names as numbers.\n///\n/// Currently exa does not support *custom* styles, where the user enters a\n/// format string in an environment variable or something. Just these four.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum TimeFormat {\n\n    /// The **default format** uses the user’s locale to print month names,\n    /// and specifies the timestamp down to the minute for recent times, and\n    /// day for older times.\n    DefaultFormat,\n\n    /// Use the **ISO format**, which specifies the timestamp down to the\n    /// minute for recent times, and day for older times. It uses a number\n    /// for the month so it doesn’t use the locale.\n    ISOFormat,\n\n    /// Use the **long ISO format**, which specifies the timestamp down to the\n    /// minute using only numbers, without needing the locale or year.\n    LongISO,\n\n    /// Use the **full ISO format**, which specifies the timestamp down to the\n    /// millisecond and includes its offset down to the minute. This too uses\n    /// only numbers so doesn’t require any special consideration.\n    FullISO,\n}\n\n// There are two different formatting functions because local and zoned\n// timestamps are separate types.\n\nimpl TimeFormat {\n    pub fn format_local(self, time: SystemTime) -> String {\n        match self {\n            Self::DefaultFormat  => default_local(time),\n            Self::ISOFormat      => iso_local(time),\n            Self::LongISO        => long_local(time),\n            Self::FullISO        => full_local(time),\n        }\n    }\n\n    pub fn format_zoned(self, time: SystemTime, zone: &TimeZone) -> String {\n        match self {\n            Self::DefaultFormat  => default_zoned(time, zone),\n            Self::ISOFormat      => iso_zoned(time, zone),\n            Self::LongISO        => long_zoned(time, zone),\n            Self::FullISO        => full_zoned(time, zone),\n        }\n    }\n}\n\n\n#[allow(trivial_numeric_casts)]\nfn default_local(time: SystemTime) -> String {\n    let date = LocalDateTime::at(systemtime_epoch(time));\n    let date_format = get_dateformat(&date);\n    date_format.format(&date, &*LOCALE)\n}\n\n#[allow(trivial_numeric_casts)]\nfn default_zoned(time: SystemTime, zone: &TimeZone) -> String {\n    let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time)));\n    let date_format = get_dateformat(&date);\n    date_format.format(&date, &*LOCALE)\n}\n\nfn get_dateformat(date: &LocalDateTime) -> &'static DateFormat<'static> {\n    match (is_recent(date), *MAXIMUM_MONTH_WIDTH) {\n        (true, 4)   => &FOUR_WIDE_DATE_TIME,\n        (true, 5)   => &FIVE_WIDE_DATE_TIME,\n        (true, _)   => &OTHER_WIDE_DATE_TIME,\n        (false, 4)  => &FOUR_WIDE_DATE_YEAR,\n        (false, 5)  => &FIVE_WIDE_DATE_YEAR,\n        (false, _)  => &OTHER_WIDE_DATE_YEAR,\n    }\n}\n\n#[allow(trivial_numeric_casts)]\nfn long_local(time: SystemTime) -> String {\n    let date = LocalDateTime::at(systemtime_epoch(time));\n    format!(\"{:04}-{:02}-{:02} {:02}:{:02}\",\n            date.year(), date.month() as usize, date.day(),\n            date.hour(), date.minute())\n}\n\n#[allow(trivial_numeric_casts)]\nfn long_zoned(time: SystemTime, zone: &TimeZone) -> String {\n    let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time)));\n    format!(\"{:04}-{:02}-{:02} {:02}:{:02}\",\n            date.year(), date.month() as usize, date.day(),\n            date.hour(), date.minute())\n}\n\n#[allow(trivial_numeric_casts)]\nfn full_local(time: SystemTime) -> String {\n    let date = LocalDateTime::at(systemtime_epoch(time));\n    format!(\"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09}\",\n            date.year(), date.month() as usize, date.day(),\n            date.hour(), date.minute(), date.second(), systemtime_nanos(time))\n}\n\n#[allow(trivial_numeric_casts)]\nfn full_zoned(time: SystemTime, zone: &TimeZone) -> String {\n    use datetime::Offset;\n\n    let local = LocalDateTime::at(systemtime_epoch(time));\n    let date = zone.to_zoned(local);\n    let offset = Offset::of_seconds(zone.offset(local) as i32).expect(\"Offset out of range\");\n    format!(\"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} {:+03}{:02}\",\n            date.year(), date.month() as usize, date.day(),\n            date.hour(), date.minute(), date.second(), systemtime_nanos(time),\n            offset.hours(), offset.minutes().abs())\n}\n\n#[allow(trivial_numeric_casts)]\nfn iso_local(time: SystemTime) -> String {\n    let date = LocalDateTime::at(systemtime_epoch(time));\n\n    if is_recent(&date) {\n        format!(\"{:02}-{:02} {:02}:{:02}\",\n                date.month() as usize, date.day(),\n                date.hour(), date.minute())\n    }\n    else {\n        format!(\"{:04}-{:02}-{:02}\",\n                date.year(), date.month() as usize, date.day())\n    }\n}\n\n#[allow(trivial_numeric_casts)]\nfn iso_zoned(time: SystemTime, zone: &TimeZone) -> String {\n    let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time)));\n\n    if is_recent(&date) {\n        format!(\"{:02}-{:02} {:02}:{:02}\",\n                date.month() as usize, date.day(),\n                date.hour(), date.minute())\n    }\n    else {\n        format!(\"{:04}-{:02}-{:02}\",\n                date.year(), date.month() as usize, date.day())\n    }\n}\n\n\nfn systemtime_epoch(time: SystemTime) -> i64 {\n    time.duration_since(UNIX_EPOCH)\n        .map(|t| t.as_secs() as i64)\n        .unwrap_or_else(|e| {\n            let diff = e.duration();\n            let mut secs = diff.as_secs();\n            if diff.subsec_nanos() > 0 {\n                secs += 1;\n            }\n            -(secs as i64)\n        })\n}\n\nfn systemtime_nanos(time: SystemTime) -> u32 {\n    time.duration_since(UNIX_EPOCH)\n        .map(|t| t.subsec_nanos())\n        .unwrap_or_else(|e| {\n            let nanos = e.duration().subsec_nanos();\n            if nanos > 0 {\n                1_000_000_000 - nanos\n            } else {\n                nanos\n            }\n        })\n}\n\nfn is_recent(date: &LocalDateTime) -> bool {\n    date.year() == *CURRENT_YEAR\n}\n\n\nlazy_static! {\n\n    static ref CURRENT_YEAR: i64 = LocalDateTime::now().year();\n\n    static ref LOCALE: locale::Time = {\n        locale::Time::load_user_locale()\n               .unwrap_or_else(|_| locale::Time::english())\n    };\n\n    static ref MAXIMUM_MONTH_WIDTH: usize = {\n        // Some locales use a three-character wide month name (Jan to Dec);\n        // others vary between three to four (1月 to 12月, juil.). We check each month width\n        // to detect the longest and set the output format accordingly.\n        let mut maximum_month_width = 0;\n        for i in 0..11 {\n            let current_month_width = UnicodeWidthStr::width(&*LOCALE.short_month_name(i));\n            maximum_month_width = std::cmp::max(maximum_month_width, current_month_width);\n        }\n        maximum_month_width\n    };\n\n    static ref FOUR_WIDE_DATE_TIME: DateFormat<'static> = DateFormat::parse(\n        \"{2>:D} {4<:M} {02>:h}:{02>:m}\"\n    ).unwrap();\n\n    static ref FIVE_WIDE_DATE_TIME: DateFormat<'static> = DateFormat::parse(\n        \"{2>:D} {5<:M} {02>:h}:{02>:m}\"\n    ).unwrap();\n\n    static ref OTHER_WIDE_DATE_TIME: DateFormat<'static> = DateFormat::parse(\n        \"{2>:D} {:M} {02>:h}:{02>:m}\"\n    ).unwrap();\n\n    static ref FOUR_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse(\n        \"{2>:D} {4<:M} {5>:Y}\"\n    ).unwrap();\n\n    static ref FIVE_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse(\n        \"{2>:D} {5<:M} {5>:Y}\"\n    ).unwrap();\n\n    static ref OTHER_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse(\n        \"{2>:D} {:M} {5>:Y}\"\n    ).unwrap();\n}\n"
  },
  {
    "path": "src/output/tree.rs",
    "content": "//! Tree structures, such as `├──` or `└──`, used in a tree view.\n//!\n//! ## Constructing Tree Views\n//!\n//! When using the `--tree` argument, instead of a vector of cells, each row\n//! has a `depth` field that indicates how far deep in the tree it is: the top\n//! level has depth 0, its children have depth 1, and *their* children have\n//! depth 2, and so on.\n//!\n//! On top of this, it also has a `last` field that specifies whether this is\n//! the last row of this particular consecutive set of rows. This doesn’t\n//! affect the file’s information; it’s just used to display a different set of\n//! Unicode tree characters! The resulting table looks like this:\n//!\n//! ```text\n//!     ┌───────┬───────┬───────────────────────┐\n//!     │ Depth │ Last  │ Output                │\n//!     ├───────┼───────┼───────────────────────┤\n//!     │     0 │       │ documents             │\n//!     │     1 │ false │ ├── this_file.txt     │\n//!     │     1 │ false │ ├── that_file.txt     │\n//!     │     1 │ false │ ├── features          │\n//!     │     2 │ false │ │  ├── feature_1.rs   │\n//!     │     2 │ false │ │  ├── feature_2.rs   │\n//!     │     2 │ true  │ │  └── feature_3.rs   │\n//!     │     1 │ true  │ └── pictures          │\n//!     │     2 │ false │    ├── garden.jpg     │\n//!     │     2 │ false │    ├── flowers.jpg    │\n//!     │     2 │ false │    ├── library.png    │\n//!     │     2 │ true  │    └── space.tiff     │\n//!     └───────┴───────┴───────────────────────┘\n//! ```\n//!\n//! Creating the table like this means that each file has to be tested to see\n//! if it’s the last one in the group. This is usually done by putting all the\n//! files in a vector beforehand, getting its length, then comparing the index\n//! of each file to see if it’s the last one. (As some files may not be\n//! successfully `stat`ted, we don’t know how many files are going to exist in\n//! each directory)\n\n\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum TreePart {\n\n    /// Rightmost column, *not* the last in the directory.\n    Edge,\n\n    /// Not the rightmost column, and the directory has not finished yet.\n    Line,\n\n    /// Rightmost column, and the last in the directory.\n    Corner,\n\n    /// Not the rightmost column, and the directory *has* finished.\n    Blank,\n}\n\nimpl TreePart {\n\n    /// Turn this tree part into ASCII-licious box drawing characters!\n    /// (Warning: not actually ASCII)\n    pub fn ascii_art(self) -> &'static str {\n        match self {\n            Self::Edge    => \"├──\",\n            Self::Line    => \"│  \",\n            Self::Corner  => \"└──\",\n            Self::Blank   => \"   \",\n        }\n    }\n}\n\n\n/// A **tree trunk** builds up arrays of tree parts over multiple depths.\n#[derive(Debug, Default)]\npub struct TreeTrunk {\n\n    /// A stack tracks which tree characters should be printed. It’s\n    /// necessary to maintain information about the previously-printed\n    /// lines, as the output will change based on any previous entries.\n    stack: Vec<TreePart>,\n\n    /// A tuple for the last ‘depth’ and ‘last’ parameters that are passed in.\n    last_params: Option<TreeParams>,\n}\n\n#[derive(Debug, Copy, Clone)]\npub struct TreeParams {\n\n    /// How many directories deep into the tree structure this is. Directories\n    /// on top have depth 0.\n    depth: TreeDepth,\n\n    /// Whether this is the last entry in the directory.\n    last: bool,\n}\n\n#[derive(Debug, Copy, Clone)]\npub struct TreeDepth(pub usize);\n\nimpl TreeTrunk {\n\n    /// Calculates the tree parts for an entry at the given depth and\n    /// last-ness. The depth is used to determine where in the stack the tree\n    /// part should be inserted, and the last-ness is used to determine which\n    /// type of tree part to insert.\n    ///\n    /// This takes a `&mut self` because the results of each file are stored\n    /// and used in future rows.\n    pub fn new_row(&mut self, params: TreeParams) -> &[TreePart] {\n\n        // If this isn’t our first iteration, then update the tree parts thus\n        // far to account for there being another row after it.\n        if let Some(last) = self.last_params {\n            self.stack[last.depth.0] = if last.last { TreePart::Blank }\n                                               else { TreePart::Line };\n        }\n\n        // Make sure the stack has enough space, then add or modify another\n        // part into it.\n        self.stack.resize(params.depth.0 + 1, TreePart::Edge);\n        self.stack[params.depth.0] = if params.last { TreePart::Corner }\n                                               else { TreePart::Edge };\n\n        self.last_params = Some(params);\n\n        // Return the tree parts as a slice of the stack.\n        //\n        // Ignore the first element here to prevent a ‘zeroth level’ from\n        // appearing before the very first directory. This level would\n        // join unrelated directories without connecting to anything:\n        //\n        //     with [0..]        with [1..]\n        //     ==========        ==========\n        //      ├── folder        folder\n        //      │  └── file       └── file\n        //      └── folder        folder\n        //         └── file       └──file\n        //\n        &self.stack[1..]\n    }\n}\n\nimpl TreeParams {\n    pub fn new(depth: TreeDepth, last: bool) -> Self {\n        Self { depth, last }\n    }\n\n    pub fn is_at_root(&self) -> bool {\n        self.depth.0 == 0\n    }\n}\n\nimpl TreeDepth {\n    pub fn root() -> Self {\n        Self(0)\n    }\n\n    pub fn deeper(self) -> Self {\n        Self(self.0 + 1)\n    }\n\n    /// Creates an iterator that, as well as yielding each value, yields a\n    /// `TreeParams` with the current depth and last flag filled in.\n    pub fn iterate_over<I, T>(self, inner: I) -> Iter<I>\n    where I: ExactSizeIterator + Iterator<Item = T>\n    {\n        Iter { current_depth: self, inner }\n    }\n}\n\n\npub struct Iter<I> {\n    current_depth: TreeDepth,\n    inner: I,\n}\n\nimpl<I, T> Iterator for Iter<I>\nwhere I: ExactSizeIterator + Iterator<Item = T>\n{\n    type Item = (TreeParams, T);\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let t = self.inner.next()?;\n\n        // TODO: use exact_size_is_empty API soon\n        let params = TreeParams::new(self.current_depth, self.inner.len() == 0);\n        Some((params, t))\n    }\n}\n\n\n#[cfg(test)]\nmod trunk_test {\n    use super::*;\n\n    fn params(depth: usize, last: bool) -> TreeParams {\n        TreeParams::new(TreeDepth(depth), last)\n    }\n\n    #[test]\n    fn empty_at_first() {\n        let mut tt = TreeTrunk::default();\n        assert_eq!(tt.new_row(params(0, true)),  &[ ]);\n    }\n\n    #[test]\n    fn one_child() {\n        let mut tt = TreeTrunk::default();\n        assert_eq!(tt.new_row(params(0, true)),  &[ ]);\n        assert_eq!(tt.new_row(params(1, true)),  &[ TreePart::Corner ]);\n    }\n\n    #[test]\n    fn two_children() {\n        let mut tt = TreeTrunk::default();\n        assert_eq!(tt.new_row(params(0, true)),  &[ ]);\n        assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);\n        assert_eq!(tt.new_row(params(1, true)),  &[ TreePart::Corner ]);\n    }\n\n    #[test]\n    fn two_times_two_children() {\n        let mut tt = TreeTrunk::default();\n        assert_eq!(tt.new_row(params(0, false)), &[ ]);\n        assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);\n        assert_eq!(tt.new_row(params(1, true)),  &[ TreePart::Corner ]);\n\n        assert_eq!(tt.new_row(params(0, true)),  &[ ]);\n        assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);\n        assert_eq!(tt.new_row(params(1, true)),  &[ TreePart::Corner ]);\n    }\n\n    #[test]\n    fn two_times_two_nested_children() {\n        let mut tt = TreeTrunk::default();\n        assert_eq!(tt.new_row(params(0, true)),  &[ ]);\n\n        assert_eq!(tt.new_row(params(1, false)), &[ TreePart::Edge ]);\n        assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Line, TreePart::Edge ]);\n        assert_eq!(tt.new_row(params(2, true)),  &[ TreePart::Line, TreePart::Corner ]);\n\n        assert_eq!(tt.new_row(params(1, true)),  &[ TreePart::Corner ]);\n        assert_eq!(tt.new_row(params(2, false)), &[ TreePart::Blank, TreePart::Edge ]);\n        assert_eq!(tt.new_row(params(2, true)),  &[ TreePart::Blank, TreePart::Corner ]);\n    }\n}\n\n\n#[cfg(test)]\nmod iter_test {\n    use super::*;\n\n    #[test]\n    fn test_iteration() {\n        let foos = &[ \"first\", \"middle\", \"last\" ];\n        let mut iter = TreeDepth::root().iterate_over(foos.iter());\n\n        let next = iter.next().unwrap();\n        assert_eq!(&\"first\", next.1);\n        assert!(!next.0.last);\n\n        let next = iter.next().unwrap();\n        assert_eq!(&\"middle\", next.1);\n        assert!(!next.0.last);\n\n        let next = iter.next().unwrap();\n        assert_eq!(&\"last\", next.1);\n        assert!(next.0.last);\n\n        assert!(iter.next().is_none());\n    }\n\n    #[test]\n    fn test_empty() {\n        let nothing: &[usize] = &[];\n        let mut iter = TreeDepth::root().iterate_over(nothing.iter());\n        assert!(iter.next().is_none());\n    }\n}\n"
  },
  {
    "path": "src/theme/default_theme.rs",
    "content": "use ansi_term::Style;\nuse ansi_term::Colour::*;\n\nuse crate::theme::ColourScale;\nuse crate::theme::ui_styles::*;\n\n\nimpl UiStyles {\n    pub fn default_theme(scale: ColourScale) -> Self {\n        Self {\n            colourful: true,\n\n            filekinds: FileKinds {\n                normal:       Style::default(),\n                directory:    Blue.bold(),\n                symlink:      Cyan.normal(),\n                pipe:         Yellow.normal(),\n                block_device: Yellow.bold(),\n                char_device:  Yellow.bold(),\n                socket:       Red.bold(),\n                special:      Yellow.normal(),\n                executable:   Green.bold(),\n            },\n\n            perms: Permissions {\n                user_read:           Yellow.bold(),\n                user_write:          Red.bold(),\n                user_execute_file:   Green.bold().underline(),\n                user_execute_other:  Green.bold(),\n\n                group_read:          Yellow.normal(),\n                group_write:         Red.normal(),\n                group_execute:       Green.normal(),\n\n                other_read:          Yellow.normal(),\n                other_write:         Red.normal(),\n                other_execute:       Green.normal(),\n\n                special_user_file:   Purple.normal(),\n                special_other:       Purple.normal(),\n\n                attribute:           Style::default(),\n            },\n\n            size: Size::colourful(scale),\n\n            users: Users {\n                user_you:           Yellow.bold(),\n                user_someone_else:  Style::default(),\n                group_yours:        Yellow.bold(),\n                group_not_yours:    Style::default(),\n            },\n\n            links: Links {\n                normal:          Red.bold(),\n                multi_link_file: Red.on(Yellow),\n            },\n\n            git: Git {\n                new:         Green.normal(),\n                modified:    Blue.normal(),\n                deleted:     Red.normal(),\n                renamed:     Yellow.normal(),\n                typechange:  Purple.normal(),\n                ignored:     Style::default().dimmed(),\n                conflicted:  Red.normal(),\n            },\n\n            punctuation:  Fixed(244).normal(),\n            date:         Blue.normal(),\n            inode:        Purple.normal(),\n            blocks:       Cyan.normal(),\n            octal:        Purple.normal(),\n            header:       Style::default().underline(),\n\n            symlink_path:         Cyan.normal(),\n            control_char:         Red.normal(),\n            broken_symlink:       Red.normal(),\n            broken_path_overlay:  Style::default().underline(),\n        }\n    }\n}\n\n\nimpl Size {\n    pub fn colourful(scale: ColourScale) -> Self {\n        match scale {\n            ColourScale::Gradient  => Self::colourful_gradient(),\n            ColourScale::Fixed     => Self::colourful_fixed(),\n        }\n    }\n\n    fn colourful_fixed() -> Self {\n        Self {\n            major:  Green.bold(),\n            minor:  Green.normal(),\n\n            number_byte: Green.bold(),\n            number_kilo: Green.bold(),\n            number_mega: Green.bold(),\n            number_giga: Green.bold(),\n            number_huge: Green.bold(),\n\n            unit_byte: Green.normal(),\n            unit_kilo: Green.normal(),\n            unit_mega: Green.normal(),\n            unit_giga: Green.normal(),\n            unit_huge: Green.normal(),\n        }\n    }\n\n    fn colourful_gradient() -> Self {\n        Self {\n            major:  Green.bold(),\n            minor:  Green.normal(),\n\n            number_byte: Fixed(118).normal(),\n            number_kilo: Fixed(190).normal(),\n            number_mega: Fixed(226).normal(),\n            number_giga: Fixed(220).normal(),\n            number_huge: Fixed(214).normal(),\n\n            unit_byte: Green.normal(),\n            unit_kilo: Green.normal(),\n            unit_mega: Green.normal(),\n            unit_giga: Green.normal(),\n            unit_huge: Green.normal(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/theme/lsc.rs",
    "content": "use std::iter::Peekable;\nuse std::ops::FnMut;\n\nuse ansi_term::{Colour, Style};\nuse ansi_term::Colour::*;\n\n\n// Parsing the LS_COLORS environment variable into a map of names to Style values.\n//\n// This is sitting around undocumented at the moment because it’s a feature\n// that should really be unnecessary! exa highlights its output by creating a\n// theme of one Style value per part of the interface that can be coloured,\n// then reading styles from that theme. The LS_COLORS variable, on the other\n// hand, can contain arbitrary characters that ls is supposed to add to the\n// output, without needing to know what they actually do. This puts exa in the\n// annoying position of having to parse the ANSI escape codes _back_ into\n// Style values before it’s able to use them. Doing this has a lot of\n// downsides: if a new terminal feature is added with its own code, exa won’t\n// be able to use this without explicit support for parsing the feature, while\n// ls would not even need to know it existed. And there are some edge cases in\n// ANSI codes, where terminals would accept codes exa is strict about it. It’s\n// just not worth doing, and there should really be a way to just use slices\n// of the LS_COLORS string without having to parse them.\n\npub struct LSColors<'var>(pub &'var str);\n\nimpl<'var> LSColors<'var> {\n    pub fn each_pair<C>(&mut self, mut callback: C)\n    where C: FnMut(Pair<'var>)\n    {\n        for next in self.0.split(':') {\n            let bits = next.split('=')\n                           .take(3)\n                           .collect::<Vec<_>>();\n\n            if bits.len() == 2 && ! bits[0].is_empty() && ! bits[1].is_empty() {\n                callback(Pair { key: bits[0], value: bits[1] });\n            }\n        }\n    }\n}\n\n\nfn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>\nwhere I: Iterator<Item = &'a str>\n{\n    match iter.peek() {\n        Some(&\"5\") => {\n            let _ = iter.next();\n            if let Some(byte) = iter.next() {\n                if let Ok(num) = byte.parse() {\n                    return Some(Fixed(num));\n                }\n            }\n        }\n\n        Some(&\"2\") => {\n            let _ = iter.next();\n            if let Some(hexes) = iter.next() {\n                // Some terminals support R:G:B instead of R;G;B\n                // but this clashes with splitting on ‘:’ in each_pair above.\n                /*if hexes.contains(':') {\n                    let rgb = hexes.splitn(3, ':').collect::<Vec<_>>();\n                    if rgb.len() != 3 {\n                        return None;\n                    }\n                    else if let (Ok(r), Ok(g), Ok(b)) = (rgb[0].parse(), rgb[1].parse(), rgb[2].parse()) {\n                        return Some(RGB(r, g, b));\n                    }\n                }*/\n\n                if let (Some(r), Some(g), Some(b)) = (hexes.parse().ok(),\n                                                      iter.next().and_then(|s| s.parse().ok()),\n                                                      iter.next().and_then(|s| s.parse().ok()))\n                {\n                    return Some(RGB(r, g, b));\n                }\n            }\n        }\n\n        _ => {},\n    }\n\n    None\n}\n\n\npub struct Pair<'var> {\n    pub key: &'var str,\n    pub value: &'var str,\n}\n\nimpl<'var> Pair<'var> {\n    pub fn to_style(&self) -> Style {\n        let mut style = Style::default();\n        let mut iter = self.value.split(';').peekable();\n\n        while let Some(num) = iter.next() {\n            match num.trim_start_matches('0') {\n\n                // Bold and italic\n                \"1\" => style = style.bold(),\n                \"2\" => style = style.dimmed(),\n                \"3\" => style = style.italic(),\n                \"4\" => style = style.underline(),\n                \"5\" => style = style.blink(),\n                // 6 is supposedly a faster blink\n                \"7\" => style = style.reverse(),\n                \"8\" => style = style.hidden(),\n                \"9\" => style = style.strikethrough(),\n\n                // Foreground colours\n                \"30\" => style = style.fg(Black),\n                \"31\" => style = style.fg(Red),\n                \"32\" => style = style.fg(Green),\n                \"33\" => style = style.fg(Yellow),\n                \"34\" => style = style.fg(Blue),\n                \"35\" => style = style.fg(Purple),\n                \"36\" => style = style.fg(Cyan),\n                \"37\" => style = style.fg(White),\n                \"38\" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.fg(c) },\n\n                // Background colours\n                \"40\" => style = style.on(Black),\n                \"41\" => style = style.on(Red),\n                \"42\" => style = style.on(Green),\n                \"43\" => style = style.on(Yellow),\n                \"44\" => style = style.on(Blue),\n                \"45\" => style = style.on(Purple),\n                \"46\" => style = style.on(Cyan),\n                \"47\" => style = style.on(White),\n                \"48\" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) },\n\n                 _   => {/* ignore the error and do nothing */},\n            }\n        }\n\n        style\n    }\n}\n\n\n#[cfg(test)]\nmod ansi_test {\n    use super::*;\n    use ansi_term::Style;\n\n    macro_rules! test {\n        ($name:ident: $input:expr => $result:expr) => {\n            #[test]\n            fn $name() {\n                assert_eq!(Pair { key: \"\", value: $input }.to_style(), $result);\n            }\n        };\n    }\n\n    // Styles\n    test!(bold:  \"1\"         => Style::default().bold());\n    test!(bold2: \"01\"        => Style::default().bold());\n    test!(under: \"4\"         => Style::default().underline());\n    test!(unde2: \"04\"        => Style::default().underline());\n    test!(both:  \"1;4\"       => Style::default().bold().underline());\n    test!(both2: \"01;04\"     => Style::default().bold().underline());\n    test!(fg:    \"31\"        => Red.normal());\n    test!(bg:    \"43\"        => Style::default().on(Yellow));\n    test!(bfg:   \"31;43\"     => Red.on(Yellow));\n    test!(bfg2:  \"0031;0043\" => Red.on(Yellow));\n    test!(all:   \"43;31;1;4\" => Red.on(Yellow).bold().underline());\n    test!(again: \"1;1;1;1;1\" => Style::default().bold());\n\n    // Failure cases\n    test!(empty: \"\"          => Style::default());\n    test!(semis: \";;;;;;\"    => Style::default());\n    test!(nines: \"99999999\"  => Style::default());\n    test!(word:  \"GREEN\"     => Style::default());\n\n    // Higher colours\n    test!(hifg:  \"38;5;149\"  => Fixed(149).normal());\n    test!(hibg:  \"48;5;1\"    => Style::default().on(Fixed(1)));\n    test!(hibo:  \"48;5;1;1\"  => Style::default().on(Fixed(1)).bold());\n    test!(hiund: \"4;48;5;1\"  => Style::default().on(Fixed(1)).underline());\n\n    test!(rgb:   \"38;2;255;100;0\"     => Style::default().fg(RGB(255, 100, 0)));\n    test!(rgbi:  \"38;2;255;100;0;3\"   => Style::default().fg(RGB(255, 100, 0)).italic());\n    test!(rgbbg: \"48;2;255;100;0\"     => Style::default().on(RGB(255, 100, 0)));\n    test!(rgbbi: \"48;2;255;100;0;3\"   => Style::default().on(RGB(255, 100, 0)).italic());\n\n    test!(fgbg:  \"38;5;121;48;5;212\"  => Fixed(121).on(Fixed(212)));\n    test!(bgfg:  \"48;5;121;38;5;212\"  => Fixed(212).on(Fixed(121)));\n    test!(toohi: \"48;5;999\"           => Style::default());\n}\n\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    macro_rules! test {\n        ($name:ident: $input:expr => $result:expr) => {\n            #[test]\n            fn $name() {\n                let mut lscs = Vec::new();\n                LSColors($input).each_pair(|p| lscs.push( (p.key.clone(), p.to_style()) ));\n                assert_eq!(lscs, $result.to_vec());\n            }\n        };\n    }\n\n    // Bad parses\n    test!(empty:    \"\"       => []);\n    test!(jibber:   \"blah\"   => []);\n\n    test!(equals:     \"=\"    => []);\n    test!(starts:     \"=di\"  => []);\n    test!(ends:     \"id=\"    => []);\n\n    // Foreground colours\n    test!(green:   \"cb=32\"   => [ (\"cb\", Green.normal()) ]);\n    test!(red:     \"di=31\"   => [ (\"di\", Red.normal()) ]);\n    test!(blue:    \"la=34\"   => [ (\"la\", Blue.normal()) ]);\n\n    // Background colours\n    test!(yellow:  \"do=43\"   => [ (\"do\", Style::default().on(Yellow)) ]);\n    test!(purple:  \"re=45\"   => [ (\"re\", Style::default().on(Purple)) ]);\n    test!(cyan:    \"mi=46\"   => [ (\"mi\", Style::default().on(Cyan)) ]);\n\n    // Bold and underline\n    test!(bold:    \"fa=1\"    => [ (\"fa\", Style::default().bold()) ]);\n    test!(under:   \"so=4\"    => [ (\"so\", Style::default().underline()) ]);\n    test!(both:    \"la=1;4\"  => [ (\"la\", Style::default().bold().underline()) ]);\n\n    // More and many\n    test!(more:  \"me=43;21;55;34:yu=1;4;1\"  => [ (\"me\", Blue.on(Yellow)), (\"yu\", Style::default().bold().underline()) ]);\n    test!(many:  \"red=31:green=32:blue=34\"  => [ (\"red\", Red.normal()), (\"green\", Green.normal()), (\"blue\", Blue.normal()) ]);\n}\n"
  },
  {
    "path": "src/theme/mod.rs",
    "content": "use ansi_term::Style;\n\nuse crate::fs::File;\nuse crate::output::file_name::Colours as FileNameColours;\nuse crate::output::render;\n\nmod ui_styles;\npub use self::ui_styles::UiStyles;\npub use self::ui_styles::Size as SizeColours;\n\nmod lsc;\npub use self::lsc::LSColors;\n\nmod default_theme;\n\n\n#[derive(PartialEq, Eq, Debug)]\npub struct Options {\n\n    pub use_colours: UseColours,\n\n    pub colour_scale: ColourScale,\n\n    pub definitions: Definitions,\n}\n\n/// Under what circumstances we should display coloured, rather than plain,\n/// output to the terminal.\n///\n/// By default, we want to display the colours when stdout can display them.\n/// Turning them on when output is going to, say, a pipe, would make programs\n/// such as `grep` or `more` not work properly. So the `Automatic` mode does\n/// this check and only displays colours when they can be truly appreciated.\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum UseColours {\n\n    /// Display them even when output isn’t going to a terminal.\n    Always,\n\n    /// Display them when output is going to a terminal, but not otherwise.\n    Automatic,\n\n    /// Never display them, even when output is going to a terminal.\n    Never,\n}\n\n#[derive(PartialEq, Eq, Debug, Copy, Clone)]\npub enum ColourScale {\n    Fixed,\n    Gradient,\n}\n\n#[derive(PartialEq, Eq, Debug, Default)]\npub struct Definitions {\n    pub ls: Option<String>,\n    pub exa: Option<String>,\n}\n\n\npub struct Theme {\n    pub ui: UiStyles,\n    pub exts: Box<dyn FileColours>,\n}\n\nimpl Options {\n\n    #[allow(trivial_casts)]   // the `as Box<_>` stuff below warns about this for some reason\n    pub fn to_theme(&self, isatty: bool) -> Theme {\n        use crate::info::filetype::FileExtensions;\n\n        if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && ! isatty) {\n            let ui = UiStyles::plain();\n            let exts = Box::new(NoFileColours);\n            return Theme { ui, exts };\n        }\n\n        // Parse the environment variables into colours and extension mappings\n        let mut ui = UiStyles::default_theme(self.colour_scale);\n        let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);\n\n        // Use between 0 and 2 file name highlighters\n        let exts = match (exts.is_non_empty(), use_default_filetypes) {\n            (false, false)  => Box::new(NoFileColours)           as Box<_>,\n            (false,  true)  => Box::new(FileExtensions)          as Box<_>,\n            ( true, false)  => Box::new(exts)                    as Box<_>,\n            ( true,  true)  => Box::new((exts, FileExtensions))  as Box<_>,\n        };\n\n        Theme { ui, exts }\n    }\n}\n\nimpl Definitions {\n\n    /// Parse the environment variables into `LS_COLORS` pairs, putting file glob\n    /// colours into the `ExtensionMappings` that gets returned, and using the\n    /// two-character UI codes to modify the mutable `Colours`.\n    ///\n    /// Also returns if the `EXA_COLORS` variable should reset the existing file\n    /// type mappings or not. The `reset` code needs to be the first one.\n    fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool) {\n        use log::*;\n\n        let mut exts = ExtensionMappings::default();\n\n        if let Some(lsc) = &self.ls {\n            LSColors(lsc).each_pair(|pair| {\n                if ! colours.set_ls(&pair) {\n                    match glob::Pattern::new(pair.key) {\n                        Ok(pat) => {\n                            exts.add(pat, pair.to_style());\n                        }\n                        Err(e) => {\n                            warn!(\"Couldn't parse glob pattern {:?}: {}\", pair.key, e);\n                        }\n                    }\n                }\n            });\n        }\n\n        let mut use_default_filetypes = true;\n\n        if let Some(exa) = &self.exa {\n            // Is this hacky? Yes.\n            if exa == \"reset\" || exa.starts_with(\"reset:\") {\n                use_default_filetypes = false;\n            }\n\n            LSColors(exa).each_pair(|pair| {\n                if ! colours.set_ls(&pair) && ! colours.set_exa(&pair) {\n                    match glob::Pattern::new(pair.key) {\n                        Ok(pat) => {\n                            exts.add(pat, pair.to_style());\n                        }\n                        Err(e) => {\n                            warn!(\"Couldn't parse glob pattern {:?}: {}\", pair.key, e);\n                        }\n                    }\n                };\n            });\n        }\n\n        (exts, use_default_filetypes)\n    }\n}\n\n\npub trait FileColours: std::marker::Sync {\n    fn colour_file(&self, file: &File<'_>) -> Option<Style>;\n}\n\n#[derive(PartialEq, Debug)]\nstruct NoFileColours;\nimpl FileColours for NoFileColours {\n    fn colour_file(&self, _file: &File<'_>) -> Option<Style> {\n        None\n    }\n}\n\n// When getting the colour of a file from a *pair* of colourisers, try the\n// first one then try the second one. This lets the user provide their own\n// file type associations, while falling back to the default set if not set\n// explicitly.\nimpl<A, B> FileColours for (A, B)\nwhere A: FileColours,\n      B: FileColours,\n{\n    fn colour_file(&self, file: &File<'_>) -> Option<Style> {\n        self.0.colour_file(file)\n            .or_else(|| self.1.colour_file(file))\n    }\n}\n\n\n#[derive(PartialEq, Debug, Default)]\nstruct ExtensionMappings {\n    mappings: Vec<(glob::Pattern, Style)>,\n}\n\n// Loop through backwards so that colours specified later in the list override\n// colours specified earlier, like we do with options and strict mode\n\nimpl FileColours for ExtensionMappings {\n    fn colour_file(&self, file: &File<'_>) -> Option<Style> {\n        self.mappings.iter().rev()\n            .find(|t| t.0.matches(&file.name))\n            .map (|t| t.1)\n    }\n}\n\nimpl ExtensionMappings {\n    fn is_non_empty(&self) -> bool {\n        ! self.mappings.is_empty()\n    }\n\n    fn add(&mut self, pattern: glob::Pattern, style: Style) {\n        self.mappings.push((pattern, style))\n    }\n}\n\n\n\n\nimpl render::BlocksColours for Theme {\n    fn block_count(&self)  -> Style { self.ui.blocks }\n    fn no_blocks(&self)    -> Style { self.ui.punctuation }\n}\n\nimpl render::FiletypeColours for Theme {\n    fn normal(&self)       -> Style { self.ui.filekinds.normal }\n    fn directory(&self)    -> Style { self.ui.filekinds.directory }\n    fn pipe(&self)         -> Style { self.ui.filekinds.pipe }\n    fn symlink(&self)      -> Style { self.ui.filekinds.symlink }\n    fn block_device(&self) -> Style { self.ui.filekinds.block_device }\n    fn char_device(&self)  -> Style { self.ui.filekinds.char_device }\n    fn socket(&self)       -> Style { self.ui.filekinds.socket }\n    fn special(&self)      -> Style { self.ui.filekinds.special }\n}\n\nimpl render::GitColours for Theme {\n    fn not_modified(&self)  -> Style { self.ui.punctuation }\n    #[allow(clippy::new_ret_no_self)]\n    fn new(&self)           -> Style { self.ui.git.new }\n    fn modified(&self)      -> Style { self.ui.git.modified }\n    fn deleted(&self)       -> Style { self.ui.git.deleted }\n    fn renamed(&self)       -> Style { self.ui.git.renamed }\n    fn type_change(&self)   -> Style { self.ui.git.typechange }\n    fn ignored(&self)       -> Style { self.ui.git.ignored }\n    fn conflicted(&self)    -> Style { self.ui.git.conflicted }\n}\n\n#[cfg(unix)]\nimpl render::GroupColours for Theme {\n    fn yours(&self)      -> Style { self.ui.users.group_yours }\n    fn not_yours(&self)  -> Style { self.ui.users.group_not_yours }\n}\n\nimpl render::LinksColours for Theme {\n    fn normal(&self)           -> Style { self.ui.links.normal }\n    fn multi_link_file(&self)  -> Style { self.ui.links.multi_link_file }\n}\n\nimpl render::PermissionsColours for Theme {\n    fn dash(&self)               -> Style { self.ui.punctuation }\n    fn user_read(&self)          -> Style { self.ui.perms.user_read }\n    fn user_write(&self)         -> Style { self.ui.perms.user_write }\n    fn user_execute_file(&self)  -> Style { self.ui.perms.user_execute_file }\n    fn user_execute_other(&self) -> Style { self.ui.perms.user_execute_other }\n    fn group_read(&self)         -> Style { self.ui.perms.group_read }\n    fn group_write(&self)        -> Style { self.ui.perms.group_write }\n    fn group_execute(&self)      -> Style { self.ui.perms.group_execute }\n    fn other_read(&self)         -> Style { self.ui.perms.other_read }\n    fn other_write(&self)        -> Style { self.ui.perms.other_write }\n    fn other_execute(&self)      -> Style { self.ui.perms.other_execute }\n    fn special_user_file(&self)  -> Style { self.ui.perms.special_user_file }\n    fn special_other(&self)      -> Style { self.ui.perms.special_other }\n    fn attribute(&self)          -> Style { self.ui.perms.attribute }\n}\n\nimpl render::SizeColours for Theme {\n    fn size(&self, prefix: Option<number_prefix::Prefix>) -> Style {\n        use number_prefix::Prefix::*;\n\n        match prefix {\n            Some(Kilo | Kibi) => self.ui.size.number_kilo,\n            Some(Mega | Mebi) => self.ui.size.number_mega,\n            Some(Giga | Gibi) => self.ui.size.number_giga,\n            Some(_)           => self.ui.size.number_huge,\n            None              => self.ui.size.number_byte,\n        }\n    }\n\n    fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {\n        use number_prefix::Prefix::*;\n\n        match prefix {\n            Some(Kilo | Kibi) => self.ui.size.unit_kilo,\n            Some(Mega | Mebi) => self.ui.size.unit_mega,\n            Some(Giga | Gibi) => self.ui.size.unit_giga,\n            Some(_)           => self.ui.size.unit_huge,\n            None              => self.ui.size.unit_byte,\n        }\n    }\n\n    fn no_size(&self) -> Style { self.ui.punctuation }\n    fn major(&self)   -> Style { self.ui.size.major }\n    fn comma(&self)   -> Style { self.ui.punctuation }\n    fn minor(&self)   -> Style { self.ui.size.minor }\n}\n\n#[cfg(unix)]\nimpl render::UserColours for Theme {\n    fn you(&self)           -> Style { self.ui.users.user_you }\n    fn someone_else(&self)  -> Style { self.ui.users.user_someone_else }\n}\n\nimpl FileNameColours for Theme {\n    fn normal_arrow(&self)        -> Style { self.ui.punctuation }\n    fn broken_symlink(&self)      -> Style { self.ui.broken_symlink }\n    fn broken_filename(&self)     -> Style { apply_overlay(self.ui.broken_symlink, self.ui.broken_path_overlay) }\n    fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char,   self.ui.broken_path_overlay) }\n    fn control_char(&self)        -> Style { self.ui.control_char }\n    fn symlink_path(&self)        -> Style { self.ui.symlink_path }\n    fn executable_file(&self)     -> Style { self.ui.filekinds.executable }\n\n    fn colour_file(&self, file: &File<'_>) -> Style {\n        self.exts.colour_file(file).unwrap_or(self.ui.filekinds.normal)\n    }\n}\n\n\n/// Some of the styles are **overlays**: although they have the same attribute\n/// set as regular styles (foreground and background colours, bold, underline,\n/// etc), they’re intended to be used to *amend* existing styles.\n///\n/// For example, the target path of a broken symlink is displayed in a red,\n/// underlined style by default. Paths can contain control characters, so\n/// these control characters need to be underlined too, otherwise it looks\n/// weird. So instead of having four separate configurable styles for “link\n/// path”, “broken link path”, “control character” and “broken control\n/// character”, there are styles for “link path”, “control character”, and\n/// “broken link overlay”, the latter of which is just set to override the\n/// underline attribute on the other two.\nfn apply_overlay(mut base: Style, overlay: Style) -> Style {\n    if let Some(fg) = overlay.foreground { base.foreground = Some(fg); }\n    if let Some(bg) = overlay.background { base.background = Some(bg); }\n\n    if overlay.is_bold          { base.is_bold          = true; }\n    if overlay.is_dimmed        { base.is_dimmed        = true; }\n    if overlay.is_italic        { base.is_italic        = true; }\n    if overlay.is_underline     { base.is_underline     = true; }\n    if overlay.is_blink         { base.is_blink         = true; }\n    if overlay.is_reverse       { base.is_reverse       = true; }\n    if overlay.is_hidden        { base.is_hidden        = true; }\n    if overlay.is_strikethrough { base.is_strikethrough = true; }\n\n    base\n}\n// TODO: move this function to the ansi_term crate\n\n\n#[cfg(test)]\nmod customs_test {\n    use super::*;\n    use crate::theme::ui_styles::UiStyles;\n    use ansi_term::Colour::*;\n\n    macro_rules! test {\n        ($name:ident:  ls $ls:expr, exa $exa:expr  =>  colours $expected:ident -> $process_expected:expr) => {\n            #[test]\n            fn $name() {\n                let mut $expected = UiStyles::default();\n                $process_expected();\n\n                let definitions = Definitions {\n                    ls:  Some($ls.into()),\n                    exa: Some($exa.into()),\n                };\n\n                let mut result = UiStyles::default();\n                let (_exts, _reset) = definitions.parse_color_vars(&mut result);\n                assert_eq!($expected, result);\n            }\n        };\n        ($name:ident:  ls $ls:expr, exa $exa:expr  =>  exts $mappings:expr) => {\n            #[test]\n            fn $name() {\n                let mappings: Vec<(glob::Pattern, Style)>\n                    = $mappings.iter()\n                               .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))\n                               .collect();\n\n                let definitions = Definitions {\n                    ls:  Some($ls.into()),\n                    exa: Some($exa.into()),\n                };\n\n                let (result, _reset) = definitions.parse_color_vars(&mut UiStyles::default());\n                assert_eq!(ExtensionMappings { mappings }, result);\n            }\n        };\n        ($name:ident:  ls $ls:expr, exa $exa:expr  =>  colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {\n            #[test]\n            fn $name() {\n                let mut $expected = UiStyles::colourful(false);\n                $process_expected();\n\n                let mappings: Vec<(glob::Pattern, Style)>\n                    = $mappings.into_iter()\n                               .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))\n                               .collect();\n\n                let definitions = Definitions {\n                    ls:  Some($ls.into()),\n                    exa: Some($exa.into()),\n                };\n\n                let mut meh = UiStyles::colourful(false);\n                let (result, _reset) = definitions.parse_color_vars(&vars, &mut meh);\n                assert_eq!(ExtensionMappings { mappings }, result);\n                assert_eq!($expected, meh);\n            }\n        };\n    }\n\n\n    // LS_COLORS can affect all of these colours:\n    test!(ls_di:   ls \"di=31\", exa \"\"  =>  colours c -> { c.filekinds.directory    = Red.normal();    });\n    test!(ls_ex:   ls \"ex=32\", exa \"\"  =>  colours c -> { c.filekinds.executable   = Green.normal();  });\n    test!(ls_fi:   ls \"fi=33\", exa \"\"  =>  colours c -> { c.filekinds.normal       = Yellow.normal(); });\n    test!(ls_pi:   ls \"pi=34\", exa \"\"  =>  colours c -> { c.filekinds.pipe         = Blue.normal();   });\n    test!(ls_so:   ls \"so=35\", exa \"\"  =>  colours c -> { c.filekinds.socket       = Purple.normal(); });\n    test!(ls_bd:   ls \"bd=36\", exa \"\"  =>  colours c -> { c.filekinds.block_device = Cyan.normal();   });\n    test!(ls_cd:   ls \"cd=35\", exa \"\"  =>  colours c -> { c.filekinds.char_device  = Purple.normal(); });\n    test!(ls_ln:   ls \"ln=34\", exa \"\"  =>  colours c -> { c.filekinds.symlink      = Blue.normal();   });\n    test!(ls_or:   ls \"or=33\", exa \"\"  =>  colours c -> { c.broken_symlink         = Yellow.normal(); });\n\n    // EXA_COLORS can affect all those colours too:\n    test!(exa_di:  ls \"\", exa \"di=32\"  =>  colours c -> { c.filekinds.directory    = Green.normal();  });\n    test!(exa_ex:  ls \"\", exa \"ex=33\"  =>  colours c -> { c.filekinds.executable   = Yellow.normal(); });\n    test!(exa_fi:  ls \"\", exa \"fi=34\"  =>  colours c -> { c.filekinds.normal       = Blue.normal();   });\n    test!(exa_pi:  ls \"\", exa \"pi=35\"  =>  colours c -> { c.filekinds.pipe         = Purple.normal(); });\n    test!(exa_so:  ls \"\", exa \"so=36\"  =>  colours c -> { c.filekinds.socket       = Cyan.normal();   });\n    test!(exa_bd:  ls \"\", exa \"bd=35\"  =>  colours c -> { c.filekinds.block_device = Purple.normal(); });\n    test!(exa_cd:  ls \"\", exa \"cd=34\"  =>  colours c -> { c.filekinds.char_device  = Blue.normal();   });\n    test!(exa_ln:  ls \"\", exa \"ln=33\"  =>  colours c -> { c.filekinds.symlink      = Yellow.normal(); });\n    test!(exa_or:  ls \"\", exa \"or=32\"  =>  colours c -> { c.broken_symlink         = Green.normal();  });\n\n    // EXA_COLORS will even override options from LS_COLORS:\n    test!(ls_exa_di: ls \"di=31\", exa \"di=32\"  =>  colours c -> { c.filekinds.directory  = Green.normal();  });\n    test!(ls_exa_ex: ls \"ex=32\", exa \"ex=33\"  =>  colours c -> { c.filekinds.executable = Yellow.normal(); });\n    test!(ls_exa_fi: ls \"fi=33\", exa \"fi=34\"  =>  colours c -> { c.filekinds.normal     = Blue.normal();   });\n\n    // But more importantly, EXA_COLORS has its own, special list of colours:\n    test!(exa_ur:  ls \"\", exa \"ur=38;5;100\"  =>  colours c -> { c.perms.user_read           = Fixed(100).normal(); });\n    test!(exa_uw:  ls \"\", exa \"uw=38;5;101\"  =>  colours c -> { c.perms.user_write          = Fixed(101).normal(); });\n    test!(exa_ux:  ls \"\", exa \"ux=38;5;102\"  =>  colours c -> { c.perms.user_execute_file   = Fixed(102).normal(); });\n    test!(exa_ue:  ls \"\", exa \"ue=38;5;103\"  =>  colours c -> { c.perms.user_execute_other  = Fixed(103).normal(); });\n    test!(exa_gr:  ls \"\", exa \"gr=38;5;104\"  =>  colours c -> { c.perms.group_read          = Fixed(104).normal(); });\n    test!(exa_gw:  ls \"\", exa \"gw=38;5;105\"  =>  colours c -> { c.perms.group_write         = Fixed(105).normal(); });\n    test!(exa_gx:  ls \"\", exa \"gx=38;5;106\"  =>  colours c -> { c.perms.group_execute       = Fixed(106).normal(); });\n    test!(exa_tr:  ls \"\", exa \"tr=38;5;107\"  =>  colours c -> { c.perms.other_read          = Fixed(107).normal(); });\n    test!(exa_tw:  ls \"\", exa \"tw=38;5;108\"  =>  colours c -> { c.perms.other_write         = Fixed(108).normal(); });\n    test!(exa_tx:  ls \"\", exa \"tx=38;5;109\"  =>  colours c -> { c.perms.other_execute       = Fixed(109).normal(); });\n    test!(exa_su:  ls \"\", exa \"su=38;5;110\"  =>  colours c -> { c.perms.special_user_file   = Fixed(110).normal(); });\n    test!(exa_sf:  ls \"\", exa \"sf=38;5;111\"  =>  colours c -> { c.perms.special_other       = Fixed(111).normal(); });\n    test!(exa_xa:  ls \"\", exa \"xa=38;5;112\"  =>  colours c -> { c.perms.attribute           = Fixed(112).normal(); });\n\n    test!(exa_sn:  ls \"\", exa \"sn=38;5;113\" => colours c -> {\n        c.size.number_byte = Fixed(113).normal();\n        c.size.number_kilo = Fixed(113).normal();\n        c.size.number_mega = Fixed(113).normal();\n        c.size.number_giga = Fixed(113).normal();\n        c.size.number_huge = Fixed(113).normal();\n    });\n    test!(exa_sb:  ls \"\", exa \"sb=38;5;114\" => colours c -> {\n        c.size.unit_byte = Fixed(114).normal();\n        c.size.unit_kilo = Fixed(114).normal();\n        c.size.unit_mega = Fixed(114).normal();\n        c.size.unit_giga = Fixed(114).normal();\n        c.size.unit_huge = Fixed(114).normal();\n    });\n\n    test!(exa_nb:  ls \"\", exa \"nb=38;5;115\"  =>  colours c -> { c.size.number_byte          = Fixed(115).normal(); });\n    test!(exa_nk:  ls \"\", exa \"nk=38;5;116\"  =>  colours c -> { c.size.number_kilo          = Fixed(116).normal(); });\n    test!(exa_nm:  ls \"\", exa \"nm=38;5;117\"  =>  colours c -> { c.size.number_mega          = Fixed(117).normal(); });\n    test!(exa_ng:  ls \"\", exa \"ng=38;5;118\"  =>  colours c -> { c.size.number_giga          = Fixed(118).normal(); });\n    test!(exa_nh:  ls \"\", exa \"nh=38;5;119\"  =>  colours c -> { c.size.number_huge          = Fixed(119).normal(); });\n\n    test!(exa_ub:  ls \"\", exa \"ub=38;5;115\"  =>  colours c -> { c.size.unit_byte            = Fixed(115).normal(); });\n    test!(exa_uk:  ls \"\", exa \"uk=38;5;116\"  =>  colours c -> { c.size.unit_kilo            = Fixed(116).normal(); });\n    test!(exa_um:  ls \"\", exa \"um=38;5;117\"  =>  colours c -> { c.size.unit_mega            = Fixed(117).normal(); });\n    test!(exa_ug:  ls \"\", exa \"ug=38;5;118\"  =>  colours c -> { c.size.unit_giga            = Fixed(118).normal(); });\n    test!(exa_uh:  ls \"\", exa \"uh=38;5;119\"  =>  colours c -> { c.size.unit_huge            = Fixed(119).normal(); });\n\n    test!(exa_df:  ls \"\", exa \"df=38;5;115\"  =>  colours c -> { c.size.major                = Fixed(115).normal(); });\n    test!(exa_ds:  ls \"\", exa \"ds=38;5;116\"  =>  colours c -> { c.size.minor                = Fixed(116).normal(); });\n\n    test!(exa_uu:  ls \"\", exa \"uu=38;5;117\"  =>  colours c -> { c.users.user_you            = Fixed(117).normal(); });\n    test!(exa_un:  ls \"\", exa \"un=38;5;118\"  =>  colours c -> { c.users.user_someone_else   = Fixed(118).normal(); });\n    test!(exa_gu:  ls \"\", exa \"gu=38;5;119\"  =>  colours c -> { c.users.group_yours         = Fixed(119).normal(); });\n    test!(exa_gn:  ls \"\", exa \"gn=38;5;120\"  =>  colours c -> { c.users.group_not_yours     = Fixed(120).normal(); });\n\n    test!(exa_lc:  ls \"\", exa \"lc=38;5;121\"  =>  colours c -> { c.links.normal              = Fixed(121).normal(); });\n    test!(exa_lm:  ls \"\", exa \"lm=38;5;122\"  =>  colours c -> { c.links.multi_link_file     = Fixed(122).normal(); });\n\n    test!(exa_ga:  ls \"\", exa \"ga=38;5;123\"  =>  colours c -> { c.git.new                   = Fixed(123).normal(); });\n    test!(exa_gm:  ls \"\", exa \"gm=38;5;124\"  =>  colours c -> { c.git.modified              = Fixed(124).normal(); });\n    test!(exa_gd:  ls \"\", exa \"gd=38;5;125\"  =>  colours c -> { c.git.deleted               = Fixed(125).normal(); });\n    test!(exa_gv:  ls \"\", exa \"gv=38;5;126\"  =>  colours c -> { c.git.renamed               = Fixed(126).normal(); });\n    test!(exa_gt:  ls \"\", exa \"gt=38;5;127\"  =>  colours c -> { c.git.typechange            = Fixed(127).normal(); });\n\n    test!(exa_xx:  ls \"\", exa \"xx=38;5;128\"  =>  colours c -> { c.punctuation               = Fixed(128).normal(); });\n    test!(exa_da:  ls \"\", exa \"da=38;5;129\"  =>  colours c -> { c.date                      = Fixed(129).normal(); });\n    test!(exa_in:  ls \"\", exa \"in=38;5;130\"  =>  colours c -> { c.inode                     = Fixed(130).normal(); });\n    test!(exa_bl:  ls \"\", exa \"bl=38;5;131\"  =>  colours c -> { c.blocks                    = Fixed(131).normal(); });\n    test!(exa_hd:  ls \"\", exa \"hd=38;5;132\"  =>  colours c -> { c.header                    = Fixed(132).normal(); });\n    test!(exa_lp:  ls \"\", exa \"lp=38;5;133\"  =>  colours c -> { c.symlink_path              = Fixed(133).normal(); });\n    test!(exa_cc:  ls \"\", exa \"cc=38;5;134\"  =>  colours c -> { c.control_char              = Fixed(134).normal(); });\n    test!(exa_bo:  ls \"\", exa \"bO=4\"         =>  colours c -> { c.broken_path_overlay       = Style::default().underline(); });\n\n    // All the while, LS_COLORS treats them as filenames:\n    test!(ls_uu:   ls \"uu=38;5;117\", exa \"\"  =>  exts [ (\"uu\", Fixed(117).normal()) ]);\n    test!(ls_un:   ls \"un=38;5;118\", exa \"\"  =>  exts [ (\"un\", Fixed(118).normal()) ]);\n    test!(ls_gu:   ls \"gu=38;5;119\", exa \"\"  =>  exts [ (\"gu\", Fixed(119).normal()) ]);\n    test!(ls_gn:   ls \"gn=38;5;120\", exa \"\"  =>  exts [ (\"gn\", Fixed(120).normal()) ]);\n\n    // Just like all other keys:\n    test!(ls_txt:  ls \"*.txt=31\",          exa \"\"  =>  exts [ (\"*.txt\",      Red.normal())             ]);\n    test!(ls_mp3:  ls \"*.mp3=38;5;135\",    exa \"\"  =>  exts [ (\"*.mp3\",      Fixed(135).normal())      ]);\n    test!(ls_mak:  ls \"Makefile=1;32;4\",   exa \"\"  =>  exts [ (\"Makefile\",   Green.bold().underline()) ]);\n    test!(exa_txt: ls \"\", exa \"*.zip=31\"           =>  exts [ (\"*.zip\",      Red.normal())             ]);\n    test!(exa_mp3: ls \"\", exa \"lev.*=38;5;153\"     =>  exts [ (\"lev.*\",      Fixed(153).normal())      ]);\n    test!(exa_mak: ls \"\", exa \"Cargo.toml=4;32;1\"  =>  exts [ (\"Cargo.toml\", Green.bold().underline()) ]);\n\n    // Testing whether a glob from EXA_COLORS overrides a glob from LS_COLORS\n    // can’t be tested here, because they’ll both be added to the same vec\n\n    // Values get separated by colons:\n    test!(ls_multi:   ls \"*.txt=31:*.rtf=32\", exa \"\"  =>  exts [ (\"*.txt\", Red.normal()),   (\"*.rtf\", Green.normal()) ]);\n    test!(exa_multi:  ls \"\", exa \"*.tmp=37:*.log=37\"  =>  exts [ (\"*.tmp\", White.normal()), (\"*.log\", White.normal()) ]);\n\n    test!(ls_five: ls \"1*1=31:2*2=32:3*3=1;33:4*4=34;1:5*5=35;4\", exa \"\"  =>  exts [\n        (\"1*1\", Red.normal()), (\"2*2\", Green.normal()), (\"3*3\", Yellow.bold()), (\"4*4\", Blue.bold()), (\"5*5\", Purple.underline())\n    ]);\n\n    // Finally, colours get applied right-to-left:\n    test!(ls_overwrite:  ls \"pi=31:pi=32:pi=33\", exa \"\"  =>  colours c -> { c.filekinds.pipe = Yellow.normal(); });\n    test!(exa_overwrite: ls \"\", exa \"da=36:da=35:da=34\"  =>  colours c -> { c.date = Blue.normal(); });\n}\n"
  },
  {
    "path": "src/theme/ui_styles.rs",
    "content": "use ansi_term::Style;\n\nuse crate::theme::lsc::Pair;\n\n\n#[derive(Debug, Default, PartialEq)]\npub struct UiStyles {\n    pub colourful: bool,\n\n    pub filekinds:  FileKinds,\n    pub perms:      Permissions,\n    pub size:       Size,\n    pub users:      Users,\n    pub links:      Links,\n    pub git:        Git,\n\n    pub punctuation:  Style,\n    pub date:         Style,\n    pub inode:        Style,\n    pub blocks:       Style,\n    pub header:       Style,\n    pub octal:        Style,\n\n    pub symlink_path:         Style,\n    pub control_char:         Style,\n    pub broken_symlink:       Style,\n    pub broken_path_overlay:  Style,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub struct FileKinds {\n    pub normal: Style,\n    pub directory: Style,\n    pub symlink: Style,\n    pub pipe: Style,\n    pub block_device: Style,\n    pub char_device: Style,\n    pub socket: Style,\n    pub special: Style,\n    pub executable: Style,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub struct Permissions {\n    pub user_read:          Style,\n    pub user_write:         Style,\n    pub user_execute_file:  Style,\n    pub user_execute_other: Style,\n\n    pub group_read:    Style,\n    pub group_write:   Style,\n    pub group_execute: Style,\n\n    pub other_read:    Style,\n    pub other_write:   Style,\n    pub other_execute: Style,\n\n    pub special_user_file: Style,\n    pub special_other:     Style,\n\n    pub attribute: Style,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub struct Size {\n    pub major: Style,\n    pub minor: Style,\n\n    pub number_byte: Style,\n    pub number_kilo: Style,\n    pub number_mega: Style,\n    pub number_giga: Style,\n    pub number_huge: Style,\n\n    pub unit_byte: Style,\n    pub unit_kilo: Style,\n    pub unit_mega: Style,\n    pub unit_giga: Style,\n    pub unit_huge: Style,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub struct Users {\n    pub user_you: Style,\n    pub user_someone_else: Style,\n    pub group_yours: Style,\n    pub group_not_yours: Style,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub struct Links {\n    pub normal: Style,\n    pub multi_link_file: Style,\n}\n\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub struct Git {\n    pub new: Style,\n    pub modified: Style,\n    pub deleted: Style,\n    pub renamed: Style,\n    pub typechange: Style,\n    pub ignored: Style,\n    pub conflicted: Style,\n}\n\nimpl UiStyles {\n    pub fn plain() -> Self {\n        Self::default()\n    }\n}\n\n\nimpl UiStyles {\n\n    /// Sets a value on this set of colours using one of the keys understood\n    /// by the `LS_COLORS` environment variable. Invalid keys set nothing, but\n    /// return false.\n    pub fn set_ls(&mut self, pair: &Pair<'_>) -> bool {\n        match pair.key {\n            \"di\" => self.filekinds.directory    = pair.to_style(),  // DIR\n            \"ex\" => self.filekinds.executable   = pair.to_style(),  // EXEC\n            \"fi\" => self.filekinds.normal       = pair.to_style(),  // FILE\n            \"pi\" => self.filekinds.pipe         = pair.to_style(),  // FIFO\n            \"so\" => self.filekinds.socket       = pair.to_style(),  // SOCK\n            \"bd\" => self.filekinds.block_device = pair.to_style(),  // BLK\n            \"cd\" => self.filekinds.char_device  = pair.to_style(),  // CHR\n            \"ln\" => self.filekinds.symlink      = pair.to_style(),  // LINK\n            \"or\" => self.broken_symlink         = pair.to_style(),  // ORPHAN\n             _   => return false,\n             // Codes we don’t do anything with:\n             // MULTIHARDLINK, DOOR, SETUID, SETGID, CAPABILITY,\n             // STICKY_OTHER_WRITABLE, OTHER_WRITABLE, STICKY, MISSING\n        }\n        true\n    }\n\n    /// Sets a value on this set of colours using one of the keys understood\n    /// by the `EXA_COLORS` environment variable. Invalid keys set nothing,\n    /// but return false. This doesn’t take the `LS_COLORS` keys into account,\n    /// so `set_ls` should have been run first.\n    pub fn set_exa(&mut self, pair: &Pair<'_>) -> bool {\n        match pair.key {\n            \"ur\" => self.perms.user_read          = pair.to_style(),\n            \"uw\" => self.perms.user_write         = pair.to_style(),\n            \"ux\" => self.perms.user_execute_file  = pair.to_style(),\n            \"ue\" => self.perms.user_execute_other = pair.to_style(),\n            \"gr\" => self.perms.group_read         = pair.to_style(),\n            \"gw\" => self.perms.group_write        = pair.to_style(),\n            \"gx\" => self.perms.group_execute      = pair.to_style(),\n            \"tr\" => self.perms.other_read         = pair.to_style(),\n            \"tw\" => self.perms.other_write        = pair.to_style(),\n            \"tx\" => self.perms.other_execute      = pair.to_style(),\n            \"su\" => self.perms.special_user_file  = pair.to_style(),\n            \"sf\" => self.perms.special_other      = pair.to_style(),\n            \"xa\" => self.perms.attribute          = pair.to_style(),\n\n            \"sn\" => self.set_number_style(pair.to_style()),\n            \"sb\" => self.set_unit_style(pair.to_style()),\n            \"nb\" => self.size.number_byte         = pair.to_style(),\n            \"nk\" => self.size.number_kilo         = pair.to_style(),\n            \"nm\" => self.size.number_mega         = pair.to_style(),\n            \"ng\" => self.size.number_giga         = pair.to_style(),\n            \"nh\" => self.size.number_huge         = pair.to_style(),\n            \"ub\" => self.size.unit_byte           = pair.to_style(),\n            \"uk\" => self.size.unit_kilo           = pair.to_style(),\n            \"um\" => self.size.unit_mega           = pair.to_style(),\n            \"ug\" => self.size.unit_giga           = pair.to_style(),\n            \"uh\" => self.size.unit_huge           = pair.to_style(),\n            \"df\" => self.size.major               = pair.to_style(),\n            \"ds\" => self.size.minor               = pair.to_style(),\n\n            \"uu\" => self.users.user_you           = pair.to_style(),\n            \"un\" => self.users.user_someone_else  = pair.to_style(),\n            \"gu\" => self.users.group_yours        = pair.to_style(),\n            \"gn\" => self.users.group_not_yours    = pair.to_style(),\n\n            \"lc\" => self.links.normal             = pair.to_style(),\n            \"lm\" => self.links.multi_link_file    = pair.to_style(),\n\n            \"ga\" => self.git.new                  = pair.to_style(),\n            \"gm\" => self.git.modified             = pair.to_style(),\n            \"gd\" => self.git.deleted              = pair.to_style(),\n            \"gv\" => self.git.renamed              = pair.to_style(),\n            \"gt\" => self.git.typechange           = pair.to_style(),\n\n            \"xx\" => self.punctuation              = pair.to_style(),\n            \"da\" => self.date                     = pair.to_style(),\n            \"in\" => self.inode                    = pair.to_style(),\n            \"bl\" => self.blocks                   = pair.to_style(),\n            \"hd\" => self.header                   = pair.to_style(),\n            \"lp\" => self.symlink_path             = pair.to_style(),\n            \"cc\" => self.control_char             = pair.to_style(),\n            \"bO\" => self.broken_path_overlay      = pair.to_style(),\n\n             _   => return false,\n        }\n\n        true\n    }\n\n    pub fn set_number_style(&mut self, style: Style) {\n        self.size.number_byte = style;\n        self.size.number_kilo = style;\n        self.size.number_mega = style;\n        self.size.number_giga = style;\n        self.size.number_huge = style;\n    }\n\n    pub fn set_unit_style(&mut self, style: Style) {\n        self.size.unit_byte = style;\n        self.size.unit_kilo = style;\n        self.size.unit_mega = style;\n        self.size.unit_giga = style;\n        self.size.unit_huge = style;\n    }\n}\n"
  },
  {
    "path": "xtests/README.md",
    "content": "# exa › xtests\n\nThese are the **extended tests**. They are integration tests: they run the `exa` binary with select configurations of parameters and environment variables, and assert that the program prints the correct text to standard output and error, and exits with the correct status code.\n\nThey test things like:\n\n- broken symlinks\n- extended attributes\n- file names with weird stuff like newlines or escapes in\n- invalid UTF-8\n- missing users and groups\n- nested Git repositories\n\nThey are intended to be run from the Vagrant VM that has already had its environment set up — see the `devtools/dev-create-test-filesystem.sh` script for how the files are generated.\n\n\n## Anatomy of the tests\n\nThe tests are run using [Specsheet](https://specsheet.software/). The TOML files define the  tests, and the files in `output/` contain the output that exa should produce.\n\nFor example, let’s look at one of the tests in `lines-view.toml`. This test checks that running exa does the right thing when running with the `-1` argument, and a directory full of files:\n\n```toml\n[[cmd]]\nname = \"‘exa -1’ displays file names, one on each line\"\nshell = \"exa -1 /testcases/file-names\"\nstdout = { file = \"outputs/names_lines.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline' ]\n```\n\nHere’s an explanation of each line:\n\n1. The `[[cmd]]` line marks this test as a [cmd](https://specsheet.software/checks/command/cmd) check, which can run arbitrary commands. In this case, the commad is exa with some arguments.\n\n2. The `name` field is a human-readable description of the feature of exa that’s under test. It gets printed to the screen as tests are run.\n\n3. The `shell` field contains the shell script to execute. It should have `exa` in there somewhere.\n\n4. The `stdout` field describes the [content](https://specsheet.software/docs/check-file-schema#content) that exa should print to standard output. In this case, the test asserts that the output of running the program should be identical to the contents of the file.\n\n5. The `stderr` field describes the content of standard error. In this case, it asserts that nothing is printed to stderr.\n\n6. The `status` field asserts that exa should exit with a status code of 0.\n\n7. The `tags` field does not change the test at all, but can be used to filter which tests are run, instead of running all of them each time.\n"
  },
  {
    "path": "xtests/attributes.toml",
    "content": "[[cmd]]\nname = \"‘exa -@lT’ produces a tree view with metadata and attribute entries\"\nshell = \"exa -@lT /testcases/attributes\"\nstdout = { file = \"outputs/attributes_xattrs_long_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'xattrs', 'long', 'tree' ]\n\n[[cmd]]\nname = \"‘exa -@T’ produces a tree view with attribute entries\"\nshell = \"exa -@T /testcases/attributes\"\nstdout = { file = \"outputs/attributes_xattrs_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'xattrs', 'tree' ]\n\n[[cmd]]\nname = \"‘exa -@T’ with file arguments produces a tree view with attribute entries\"\nshell = \"exa -@T /testcases/attributes/*\"\nstdout = { file = \"outputs/attributes_files_xattrs_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'xattrs', 'tree' ]\n\n[[cmd]]\nname = \"‘exa -@T’ produces a tree view with attribute entries of symlinks\"\nshell = \"exa -@T /testcases/links\"\nstdout = { file = \"outputs/links_xattrs_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'xattrs', 'tree' ]\n\n\n# permission errors tests\n\n[[cmd]]\nname = \"‘exa -@T’ displays an inaccessible directory with errors\"\nshell = \"exa -@T /proc/1/root\"\nstdout = { file = \"outputs/proc_1_root_xattrs.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree' ]\n"
  },
  {
    "path": "xtests/colour-term.toml",
    "content": "# details view (check the argument works)\n\n[[cmd]]\nname = \"‘exa -l --colour=always’ always uses colours for metadata\"\nshell = \"exa -l --colour=always /testcases/files\"\nstdout = { file = \"outputs/files_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'colour-term' ]\n\n[[cmd]]\nname = \"‘exa -l --colour=never’ never uses colours for metadata\"\nshell = \"exa -l --colour=never /testcases/files\"\nstdout = { file = \"outputs/files_long_monochrome.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'colour-term' ]\n\n[[cmd]]\nname = \"‘exa -l --colour=automatic’ uses colours dependently for metadata\"\nshell = \"exa -l --colour=automatic /testcases/files\"\nstdout = { file = \"outputs/files_long_monochrome.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'colour-term' ]\n\n\n# grid view (check that all colours are turned off)\n\n[[cmd]]\nname = \"‘exa --colour=never’ never uses colours for file names\"\nshell = \"exa --colour=never /testcases/file-names\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/files_grid_monochrome.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'grid', 'colour-term' ]\n\n[[cmd]]\nname = \"‘exa --colour=never’ never uses colours for files based on their extensions\"\nshell = \"exa --colour=never /testcases/file-names-exts\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/exts_grid_monochrome.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'grid', 'colour-term' ]\n\n\n# tree view (check that all colours are turned off)\n\n[[cmd]]\nname = \"‘exa -T --colour=never’ never uses colours for punctuation and symlink targets\"\nshell = \"exa -T --colour=never /testcases/file-names/links\"\nstdout = { file = \"outputs/links_grid_monochrome.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree', 'colour-term' ]\n"
  },
  {
    "path": "xtests/debug-logging.toml",
    "content": "[[cmd]]\nname = \"‘EXA_DEBUG=1 exa’ produces debug output\"\nshell = \"exa --long /testcases\"\nenvironment = { EXA_DEBUG = \"1\" }\nstdout = { empty = false }\nstderr = { string = \"DEBUG\" }\nstatus = 0\ntags = [ 'debug', 'env', 'long' ]\n\n[[cmd]]\nname = \"‘EXA_DEBUG=trace exa’ produces trace-level debug output\"\nshell = \"exa --long /testcases\"\nenvironment = { EXA_DEBUG = \"trace\" }\nstdout = { empty = false }\nstderr = { string = \"TRACE\" }\nstatus = 0\ntags = [ 'debug', 'env', 'long' ]\n"
  },
  {
    "path": "xtests/details-view-dates.toml",
    "content": "# various date fields\n\n[[cmd]]\nname = \"‘exa -lh’ produces a table using the modified time field\"\nshell = \"exa -lh /testcases/dates\"\nstdout = { file = \"outputs/dates_long_time_modified.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'header', 'time' ]\n\n[[cmd]]\nname = \"‘exa -lh --time=modified’ produces a table using the modified time field\"\nshell = \"exa -lh --time=modified /testcases/dates\"\nstdout = { file = \"outputs/dates_long_time_modified.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'header', 'time' ]\n\n[[cmd]]\nname = \"‘exa -lh --time=accessed’ produces a table using the accessed time field\"\nshell = \"exa -lh --time=accessed /testcases/dates\"\nstdout = { file = \"outputs/dates_long_time_accessed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'header', 'time' ]\n\n\n# distant past and far future dates\n\n[[cmd]]\nname = \"‘exa -l’ handles dates far past and future dates\"\nshell = \"exa -l /testcases/far-dates\"\nstdout = { file = \"outputs/far_dates_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'time' ]\n\n\n# alternate date formats\n\n[[cmd]]\nname = \"‘exa -l --time-style=long-iso’ produces a table using the long-iso date format\"\nshell = \"exa -l --time-style=long-iso /testcases/dates\"\nstdout = { file = \"outputs/dates_long_timestyle_longiso.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'time-style' ]\n\n[[cmd]]\nname = \"‘exa -l --time-style=full-iso’ produces a table using the full-iso date format\"\nshell = \"exa -l --time-style=full-iso /testcases/dates\"\nstdout = { file = \"outputs/dates_long_timestyle_fulliso.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'time-style' ]\n\n[[cmd]]\nname = \"‘exa -l --time-style=iso’ produces a table using the iso date format\"\nshell = \"exa -l --time-style=iso /testcases/dates\"\nstdout = { file = \"outputs/dates_long_timestyle_iso.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'time-style' ]\n\n\n# locales\n\n[[cmd]]\nname = \"‘exa -l’ using a locale with 4-character-long month abbreviations (‘ja_JP’) sizes the date column correctly\"\nshell = \"exa -l /testcases/dates\"\nenvironment = { LC_TIME = \"ja_JP.UTF-8\", LANG = \"ja_JP.UTF-8\" }\nstdout = { file = \"outputs/dates_long_localejp.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'locales' ]\n\n[[cmd]]\nname = \"‘exa -l’ using a locale with 5-character-long month abbreviations (‘fr_FR’) sizes the date column correctly\"\nshell = \"exa -l /testcases/dates\"\nenvironment = { LC_TIME = \"fr_FR.UTF-8\", LANG = \"fr_FR.UTF-8\" }\nstdout = { file = \"outputs/dates_long_localefr.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'locales' ]\n\n[[cmd]]\nname = \"‘exa -l’ using a locale (‘fr_FR’) display dates of the current year with localized month name\"\nshell = \"exa -l /testcases/files\"\nenvironment = { LC_TIME = \"fr_FR.UTF-8\", LANG = \"fr_FR.UTF-8\" }\nstdout = { file = \"outputs/dates_long_currentyear_localefr.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'locales' ]\n"
  },
  {
    "path": "xtests/details-view-filesizes.toml",
    "content": "[[cmd]]\nname = \"‘exa -lb’ produces a details table with binary file sizes\"\nshell = \"exa -lb /testcases/files\"\nstdout = { file = \"outputs/files_long_binary.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'binary' ]\n\n[[cmd]]\nname = \"‘exa -lB’ produces a details table with bytes file sizes\"\nshell = \"exa -lB /testcases/files\"\nstdout = { file = \"outputs/files_long_bytes.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'bytes' ]\n\n[[cmd]]\nname = \"‘exa -lhb’ produces a details table with a header and binary file sizes\"\nshell = \"exa -lhb /testcases/files\"\nstdout = { file = \"outputs/files_long_header_binary.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'header', 'binary' ]\n\n[[cmd]]\nname = \"‘exa -lhB’ produces a details table with a header and bytes file sizes\"\nshell = \"exa -lhB /testcases/files\"\nstdout = { file = \"outputs/files_long_header_bytes.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'header', 'bytes' ]\n\n[[cmd]]\nname = \"‘exa -l --color-scale’ (US spelling) produces a details table using a file size colour scale\"\nshell = \"exa -l --color-scale /testcases/files\"\nstdout = { file = \"outputs/files_long_colourscale.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'colour-scale' ]\n\n[[cmd]]\nname = \"‘exa -l --colour-scale’ (UK spelling) produces a details table using a file size colour scale\"\nshell = \"exa -l --colour-scale /testcases/files\"\nstdout = { file = \"outputs/files_long_colourscale.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'colour-scale' ]\n\n[[cmd]]\nname = \"‘exa -l --colour-scale --binary’ produces a details table using a file size colour scale and binary sizes\"\nshell = \"exa -l --colour-scale --binary /testcases/files\"\nstdout = { file = \"outputs/files_long_colourscale_binary.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'colour-scale', 'binary' ]\n\n[[cmd]]\nname = \"‘exa -l --colour-scale --bytes’ produces a details table using a file size colour scale and byte sizes\"\nshell = \"exa -l --colour-scale --bytes /testcases/files\"\nstdout = { file = \"outputs/files_long_colourscale_bytes.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'colour-scale', 'bytes' ]\n\n[[cmd]]\nname = \"‘exa -l’ produces a details table with major and minor device IDs\"\nshell = \"cd /dev; exa -l mem null port zero full random urandom --sort=none --no-time\"\nstdout = { file = \"outputs/dev_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'dev' ]\n\n# these particular device IDs should be fixed:\n# https://raw.githubusercontent.com/torvalds/linux/master/Documentation/admin-guide/devices.txt\n"
  },
  {
    "path": "xtests/details-view-passwd.toml",
    "content": "[[cmd]]\nname = \"‘exa -lgh’ produces a tree view with attribute entries\"\nshell = \"exa -lgh /testcases/passwd\"\nstdout = { file = \"outputs/passwd_long_group_header.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'group', 'header' ]\n"
  },
  {
    "path": "xtests/details-view-permissions.toml",
    "content": "[[cmd]]\nname = \"‘exa -lghR’ (not as the user) produces a tree view with attribute entries\"\nshell = \"exa -lghR /testcases/permissions\"\nstdout = { file = \"outputs/permissions_long_group_header.ansitxt\" }\nstderr = { string = \"/testcases/permissions/forbidden-directory: Permission denied (os error 13)\" }\nstatus = 0\ntags = [ 'long', 'group', 'header', 'xattrs' ]\n\n[[cmd]]\nname = \"‘exa -lghR’ (as the user) produces a tree view with attribute entries\"\nshell = \"sudo -u cassowary exa -lghR /testcases/permissions\"\nstdout = { file = \"outputs/permissions_long_group_header_sudo.ansitxt\" }\nstderr = { string = \"/testcases/permissions/forbidden-directory: Permission denied (os error 13)\" }\nstatus = 0\ntags = [ 'long', 'group', 'header', 'xattrs', 'sudo' ]\n"
  },
  {
    "path": "xtests/details-view.toml",
    "content": "[[cmd]]\nname = \"‘exa -l’ produces a details table\"\nshell = \"exa -l /testcases/files\"\nstdout = { file = \"outputs/files_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long' ]\n\n\n# header tests\n\n[[cmd]]\nname = \"‘exa -lh’ produces a details table with a header\"\nshell = \"exa -lh /testcases/files\"\nstdout = { file = \"outputs/files_long_header.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'header' ]\n\n[[cmd]]\nname = \"‘exa -lh’ with an empty directory skips the header\"\nshell = \"exa -lh /testcases/empty\"\nstdout = { empty = true }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'header' ]\n\n\n# file kinds\n\n[[cmd]]\nname = \"‘exa -l’ handles file kinds\"\nshell = \"exa -l /testcases/specials\"\nstdout = { file = \"outputs/specials_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long' ]\n\n[[cmd]]\nname = \"‘exa -lF’ handles and classifies file kinds\"\nshell = \"exa -lF /testcases/specials\"\nstdout = { file = \"outputs/specials_long_classify.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'classify' ]\n\n[[cmd]]\nname = \"‘exa -lF’ handles and classifies symlink kinds\"\nshell = \"exa -lF --no-time /testcases/links\"\nstdout = { file = \"outputs/links_long_classify.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'classify' ]\n"
  },
  {
    "path": "xtests/dotfiles.toml",
    "content": "# hidden files in grid view\n\n[[cmd]]\nname = \"‘exa’ does not show hidden files (in grid view)\"\nshell = \"exa /testcases/hiddens\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/hiddens_grid.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'all', 'grid' ]\n\n[[cmd]]\nname = \"‘exa -a’ shows hidden files (in grid view)\"\nshell = \"exa -a /testcases/hiddens\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/hiddens_grid_all.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'all', 'grid' ]\n\n[[cmd]]\nname = \"‘exa -aa’ shows hidden files, ., and .. (in grid view)\"\nshell = \"exa -aa /testcases/hiddens\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/hiddens_grid_all_all.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'all', 'grid' ]\n\n\n# hidden files in long view\n\n[[cmd]]\nname = \"‘exa -l’ does not show hidden files (in details view)\"\nshell = \"exa -l /testcases/hiddens\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/hiddens_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'all', 'long' ]\n\n[[cmd]]\nname = \"‘exa -la’ shows hidden files (in details view)\"\nshell = \"exa -la /testcases/hiddens\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/hiddens_long_all.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'all', 'long' ]\n\n[[cmd]]\nname = \"‘exa -laa’ shows hidden files, ., and .. (in details view)\"\nshell = \"exa -laa /testcases/hiddens\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/hiddens_long_all_all.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'all', 'long' ]\n"
  },
  {
    "path": "xtests/errors.toml",
    "content": "# Command-line errors\n\n[[cmd]]\nname = \"‘exa --aoeu’ displays an error\"\nshell = \"exa --aoeu\"\nstdout = { empty = true }\nstderr = { file = \"outputs/error_invalid_option.ansitxt\" }\nstatus = 3\ntags = [ 'error' ]\n\n[[cmd]]\nname = \"‘exa -Taa’ displays an error\"\nshell = \"exa -Taa\"\nstdout = { empty = true }\nstderr = { file = \"outputs/error_tree_all_all.ansitxt\" }\nstatus = 3\ntags = [ 'error' ]\n\n\n# Error suggestions\n\n[[cmd]]\nname = \"‘exa -ltr’ offers a suggestion\"\nshell = \"exa -ltr\"\nstdout = { empty = true }\nstderr = { string = \"To sort oldest files last, try \\\"--sort oldest\\\", or just \\\"-sold\\\"\"}\nstatus = 3\ntags = [ 'error', 'long', 'sort' ]\n\n[[cmd]]\nname = \"‘exa -lt’ offers a suggestion\"\nshell = \"exa -lt\"\nstdout = { empty = true }\nstderr = { string = \"To sort newest files last, try \\\"--sort newest\\\", or just \\\"-snew\\\"\"}\nstatus = 3\ntags = [ 'error', 'long', 'sort' ]\n\n\n# Invalid values for $COLUMNS\n\n[[cmd]]\nname = \"‘COLUMNS=999... exa’ shows an error about the number size\"\nshell = \"exa\"\nenvironment = { \"COLUMNS\" = \"99999999999999999999999\" }\nstdout = { empty = true }\nstderr = { file = \"outputs/error_columns_nines.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'env' ]\n\n[[cmd]]\nname = \"‘COLUMNS=abcdef exa’ shows an error about invalid digits\"\nshell = \"exa\"\nenvironment = { \"COLUMNS\" = \"abcdef\" }\nstdout = { empty = true }\nstderr = { file = \"outputs/error_columns_invalid.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'env' ]\n\n\n# Invalid values for $EXA_GRID_ROWS\n\n[[cmd]]\nname = \"‘EXA_GRID_ROWS=999... exa -lG’ shows an error about the number size\"\nshell = \"exa -lG\"\nenvironment = { \"EXA_GRID_ROWS\" = \"99999999999999999999999\" }\nstdout = { empty = true }\nstderr = { file = \"outputs/error_grid_rows_nines.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'env' ]\n\n[[cmd]]\nname = \"‘EXA_GRID_ROWS=abcdef exa -lG’ shows an error about invalid digits\"\nshell = \"exa -lG\"\nenvironment = { \"EXA_GRID_ROWS\" = \"abcdef\" }\nstdout = { empty = true }\nstderr = { file = \"outputs/error_grid_rows_invalid.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'env' ]\n\n\n# Invalid values for $EXA_ICON_SPACING\n\n[[cmd]]\nname = \"‘EXA_ICON_SPACING=999... exa --icons’ shows an error about the number size\"\nshell = \"exa --icons\"\nenvironment = { \"EXA_ICON_SPACING\" = \"99999999999999999999999\" }\nstdout = { empty = true }\nstderr = { file = \"outputs/error_icon_spacing_nines.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'env', 'icons' ]\n\n[[cmd]]\nname = \"‘EXA_ICON_SPACING=abcdef exa --icons’ shows an error about invalid digits\"\nshell = \"exa --icons\"\nenvironment = { \"EXA_ICON_SPACING\" = \"abcdef\" }\nstdout = { empty = true }\nstderr = { file = \"outputs/error_icon_spacing_invalid.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'env', 'icons' ]\n\n\n# Invalid values for --level (-L)\n\n[[cmd]]\nname = \"‘exa -TL999...’ shows an error about the number size\"\nshell = \"exa -TL99999999999999999999999\"\nstdout = { empty = true }\nstderr = { file = \"outputs/error_level_nines.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'tree', 'level' ]\n\n[[cmd]]\nname = \"‘exa -TLabcdef’ shows an error about invalid digits\"\nshell = \"exa -TLabcdef\"\nstdout = { empty = true }\nstderr = { file = \"outputs/error_level_invalid.ansitxt\" }\nstatus = 3\ntags = [ 'error', 'tree', 'level' ]\n"
  },
  {
    "path": "xtests/features/none.toml",
    "content": "# These tests are meant to be run against an exa binary compiled with\n# `--no-default-features`. They will fail otherwise.\n\n\n[[cmd]]\nname = \"The missing features are documented in the version\"\nshell = \"exa --version\"\nstdout = { string = \"[-git]\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'features' ]\n\n[[cmd]]\nname = \"The ‘--git’ option is not accepted when the feature is disabled\"\nshell = \"exa --git\"\nstdout = { empty = true }\nstderr = { file = \"outputs/disabled_git.txt\" }\nstatus = 3\ntags = [ 'features' ]\n\n[[cmd]]\nname = \"The ‘--git-ignore option is not accepted when the feature is disabled\"\nshell = \"exa --git-ignore\"\nstdout = { empty = true }\nstderr = { file = \"outputs/disabled_git.txt\" }\nstatus = 3\ntags = [ 'features' ]\n"
  },
  {
    "path": "xtests/features/outputs/disabled_git.txt",
    "content": "exa: Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa\n"
  },
  {
    "path": "xtests/git-ignore.toml",
    "content": "# Git-ignoring\n\n[[cmd]]\nname = \"‘exa --git-ignore’ skips Git-ignored files\"\nshell = \"exa --git-ignore /testcases/git2/ignoreds\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/git2_ignoreds_grid_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -1’ skips Git-ignored files\"\nshell = \"exa --git-ignore -1 /testcases/git2/ignoreds\"\nstdout = { file = \"outputs/git2_ignoreds_lines_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -l’ skips Git-ignored files\"\nshell = \"exa --git-ignore -l /testcases/git2/ignoreds\"\nstdout = { file = \"outputs/git2_ignoreds_long_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -lG’ skips Git-ignored files\"\nshell = \"exa --git-ignore -lG /testcases/git2/ignoreds\"\nenvironment = { COLUMNS = \"150\" }\nstdout = { file = \"outputs/git2_ignoreds_long_grid_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -lR’ skips Git-ignored files\"\nshell = \"exa --git-ignore -lR /testcases/git2/ignoreds\"\nstdout = { file = \"outputs/git2_ignoreds_long_recurse_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'recurse', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -lT’ skips Git-ignored files\"\nshell = \"exa --git-ignore -lT /testcases/git2/ignoreds\"\nstdout = { file = \"outputs/git2_ignoreds_long_tree_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'tree', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -T’ skips Git-ignored files\"\nshell = \"exa --git-ignore -T /testcases/git2/ignoreds\"\nstdout = { file = \"outputs/git2_ignoreds_tree_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree', 'git-ignore' ]\n\n\n# Recursive git-ignoring\n\n[[cmd]]\nname = \"‘exa --git-ignore -lR’ skips Git-ignored files in subfolders\"\nshell = \"exa --git-ignore -lR /testcases/git2\"\nstdout = { file = \"outputs/git2_long_recurse_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'recurse', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -lT’ skips Git-ignored files in subfolders\"\nshell = \"exa --git-ignore -lT /testcases/git2\"\nstdout = { file = \"outputs/git2_long_tree_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'tree', 'git-ignore' ]\n\n[[cmd]]\nname = \"‘exa --git-ignore -T’ skips Git-ignored files in subfolders\"\nshell = \"exa --git-ignore -T /testcases/git2\"\nstdout = { file = \"outputs/git2_tree_gitignore.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree', 'git-ignore' ]\n"
  },
  {
    "path": "xtests/git.toml",
    "content": "# The first Git repo: additions and modifications\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column\"\nshell = \"exa --git -l /testcases/git\"\nstdout = { file = \"outputs/git1_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -lR’ shows a Git status column in every table\"\nshell = \"exa --git -lR /testcases/git\"\nstdout = { file = \"outputs/git1_long_recurse.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -lT’ shows a Git status column alongside the tree\"\nshell = \"exa --git -lT /testcases/git\"\nstdout = { file = \"outputs/git1_long_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ with a directory argument shows the combined Git status column\"\nshell = \"exa --git -l /testcases/git/moves/thither\"\nstdout = { file = \"outputs/git1_long_moves.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column containing new files\"\nshell = \"exa --git -l /testcases/git/additions\"\nstdout = { file = \"outputs/git1_long_additions.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column containing modified files\"\nshell = \"exa --git -l /testcases/git/edits\"\nstdout = { file = \"outputs/git1_long_edits.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column containing multiple statuses\"\nshell = \"exa --git -l /testcases/git/{additions,edits}\"\nstdout = { file = \"outputs/git1_long_multiple.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -lGd’ with file arguments shows a Git status column\"\nshell = \"exa --git -lGd /testcases/git/**/* /testcases\"\nenvironment = { COLUMNS = \"150\" }\nstdout = { file = \"outputs/git1_paths_long_grid.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'grid', 'git', 'list-dirs' ]\n\n\n# The second Git repo: nested repositories and file ignoring\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column with ignored statuses\"\nshell = \"exa --git -l /testcases/git2\"\nstdout = { file = \"outputs/git2_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -lR’ shows a Git status column in every table, handling ignored files and nested repositories\"\nshell = \"exa --git -lR /testcases/git2\"\nstdout = { file = \"outputs/git2_long_recurse.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -lT’ shows a Git status column alongside the tree, handling ignored files and nested repositories\"\nshell = \"exa --git -lT /testcases/git2\"\nstdout = { file = \"outputs/git2_long_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ with a directory argument shows ignored flags inside a directory\"\nshell = \"exa --git -l /testcases/git2/ignoreds\"\nstdout = { file = \"outputs/git2_long_ignorednested.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ with an ignored directory argument flags the contents as ignored\"\nshell = \"exa --git -l /testcases/git2/target\"\nstdout = { file = \"outputs/git2_long_ignoreddir.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l --list-dirs’ with a directory argument doesn’t flag it as ignored if only the content is\"\nshell = \"exa --git -l --list-dirs /testcases/git2/ignoreds/nested2\"\nstdout = { file = \"outputs/git2_long_ignoredcontent.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ with a nested repository argument uses the sub-repository rules\"\nshell = \"exa --git -l /testcases/git2/deeply/nested/repository\"\nstdout = { file = \"outputs/git2_long_nested.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ with multiple directory arguments still gets the flags correct\"\nshell = \"exa --git -l /testcases/git2/{deeply,ignoreds,target}\"\nstdout = { file = \"outputs/git2_long_multiple.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n\n# The third Git repo: broken symlinks\n\n[[cmd]]\nname = \"‘exa --git -l’ handles broken symlinks in Git repositories\"\nshell = \"exa --git -l /testcases/git3\"\nstdout = { file = \"outputs/git3_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n\n\n# The forth Git repo: non UTF-8 file\n\n[[cmd]]\nname = \"‘exa --git -l’ handles non UTF8 file in Git repositories\"\nshell = \"exa --git -l /testcases/git4\"\nstdout = { file = \"outputs/git4_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n\n# Both repositories 1 and 2 at once\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column for multiple repositories\"\nshell = \"exa --git -l /testcases/git /testcases/git2\"\nstdout = { file = \"outputs/git1+2_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column for multiple repositories across multiple directories\"\nshell = \"exa --git -l /testcases/{git/additions,git2/deeply,git/edits,git2/deeply/nested}\"\nstdout = { file = \"outputs/git1+2_long_directories.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column for multiple repositories across multiple directories 2\"\nshell = \"exa --git -l /testcases/{git2/deeply/nested/directory,git/edits,git2/target,git2/deeply,git}\"\nstdout = { file = \"outputs/git1+2_long_nested.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n\n# No repository present\n\n[[cmd]]\nname = \"‘exa --git -l’ shows an empty status for no repository\"\nshell = \"exa --git -l /testcases/files\"\nstdout = { file = \"outputs/files_long.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'git' ]\n\n[[cmd]]\nname = \"‘exa --git -lG’ shows an empty status for no repository\"\nshell = \"exa --git -lG /testcases/files\"\nenvironment = { COLUMNS = \"40\" }\nstdout = { file = \"outputs/files_long_grid_1col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid', 'git' ]\n"
  },
  {
    "path": "xtests/grid-details-view.toml",
    "content": "# listing directory tests\n\n[[cmd]]\nname = \"‘COLUMNS=40 exa -lG’ produces a grid with details of 1 column\"\nshell = \"exa -lG /testcases/files\"\nenvironment = { COLUMNS = \"40\" }\nstdout = { file = \"outputs/files_long_grid_1col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=80 exa -lG’ produces a grid with details of 1 column\"\nshell = \"exa -lG /testcases/files\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/files_long_grid_1col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=120 exa -lG’ produces a grid with details of 2 columns\"\nshell = \"exa -lG /testcases/files\"\nenvironment = { COLUMNS = \"120\" }\nstdout = { file = \"outputs/files_long_grid_2col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=160 exa -lG’ produces a grid with details of 3 columns\"\nshell = \"exa -lG /testcases/files\"\nenvironment = { COLUMNS = \"160\" }\nstdout = { file = \"outputs/files_long_grid_3col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=200 exa -lG’ produces a grid with details of 4 columns\"\nshell = \"exa -lG /testcases/files\"\nenvironment = { COLUMNS = \"200\" }\nstdout = { file = \"outputs/files_long_grid_4col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n\n# listing files tests\n# (these rely on bash’s glob sort order)\n# (some of the output files also have trailing whitespace)\n\n[[cmd]]\nname = \"‘COLUMNS=100 exa -lG’ with file arguments produces a grid with details of 1 column, with full paths\"\nshell = \"exa -lG /testcases/files/*\"\nenvironment = { COLUMNS = \"100\" }\nstdout = { file = \"outputs/files_paths_long_grid_1col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=150 exa -lG’ with file arguments produces a grid with details of 2 columns, with full paths\"\nshell = \"exa -lG /testcases/files/*\"\nenvironment = { COLUMNS = \"150\" }\nstdout = { file = \"outputs/files_paths_long_grid_2col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=200 exa -lG’ with file arguments produces a grid with details of 3 columns, with full paths\"\nshell = \"exa -lG /testcases/files/*\"\nenvironment = { COLUMNS = \"200\" }\nstdout = { file = \"outputs/files_paths_long_grid_3col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n\n# check if exa is using the minimum number of columns with headers\n\n[[cmd]]\nname = \"‘COLUMNS=200 exa -lGh’ with one file don’t produce extra columns even if there place for more\"\nshell = \"exa -lGh /testcases/files/10_bytes\"\nenvironment = { COLUMNS = \"200\" }\nstdout = { file = \"outputs/files_long_grid_header_1file.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=200 exa -lGh’ with several files don’t produce extra columns even if there place for more\"\nshell = \"exa -lGh /testcases/files/10_{bytes,KiB}\"\nenvironment = { COLUMNS = \"200\" }\nstdout = { file = \"outputs/files_long_grid_header_2files.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n\n# check if EXA_GRID_ROWS is working\n\n[[cmd]]\nname = \"‘COLUMNS=200 EXA_GRID_ROWS=2 exa -lG’ with three files produces a grid details of 1 column\"\nshell = \"exa -lG /testcases/files/1_*\"\nenvironment = { COLUMNS = \"200\", EXA_GRID_ROWS = \"2\" }\nstdout = { file = \"outputs/files_long_grid_exa_grid_rows_2_3files.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=200 EXA_GRID_ROWS=5 exa -lG’ with 15 files produces a grid details of 3 columns\"\nshell = \"exa -lG /testcases/files/1*\"\nenvironment = { COLUMNS = \"200\", EXA_GRID_ROWS = \"5\" }\nstdout = { file = \"outputs/files_long_grid_exa_grid_rows_5_15files.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=200 EXA_GRID_ROWS=6 exa -lG’ with 15 files produces a grid details of 1 column\"\nshell = \"exa -lG /testcases/files/1*\"\nenvironment = { COLUMNS = \"200\", EXA_GRID_ROWS = \"6\" }\nstdout = { file = \"outputs/files_long_grid_exa_grid_rows_6_15files.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid' ]\n"
  },
  {
    "path": "xtests/grid-view.toml",
    "content": "# file name tests\n\n[[cmd]]\nname = \"‘exa’ produces a grid of file names\"\nshell = \"exa /testcases/file-names\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/names_grid.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n[[cmd]]\nname = \"‘exa -x’ produces an across grid of file names\"\nshell = \"exa -x /testcases/file-names\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/names_grid_across.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid', 'across' ]\n\n[[cmd]]\nname = \"‘exa -d’ displays, ‘.’, ‘..’, and ‘/’ correctly\"\nshell = \"exa -d . .. /\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/dirs_grid.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid', 'list-dirs' ]\n\n\n# recurse tests\n\n[[cmd]]\nname = \"‘exa -R’ produces several grids of file names\"\nshell = \"exa -R /testcases/file-names\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/names_grid_recurse.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid', 'recurse' ]\n\n\n# symlink tests\n\n[[cmd]]\nname = \"‘exa’ highlights symlinks and broken symlinks\"\nshell = \"exa /testcases/links\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/links_grid.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n\n\n# columns and width tests\n\n[[cmd]]\nname = \"‘COLUMNS=40 exa’ produces a grid of 4 columns\"\nshell = \"exa /testcases/files\"\nenvironment = { COLUMNS = \"40\" }\nstdout = { file = \"outputs/files_grid_4col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=80 exa’ produces a grid of 8 columns\"\nshell = \"exa /testcases/files\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/files_grid_8col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=120 exa’ produces a grid of 13 columns\"\nshell = \"exa /testcases/files\"\nenvironment = { COLUMNS = \"120\" }\nstdout = { file = \"outputs/files_grid_13col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=160 exa’ produces a grid of 13 columns\"\nshell = \"exa /testcases/files\"\nenvironment = { COLUMNS = \"160\" }\nstdout = { file = \"outputs/files_grid_13col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=200 exa’ produces a grid of 20 columns\"\nshell = \"exa /testcases/files\"\nenvironment = { COLUMNS = \"200\" }\nstdout = { file = \"outputs/files_grid_20col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n\n# columns and width tests with files\n# (warning: some of the output files have trailing whitespace)\n\n[[cmd]]\nname = \"‘COLUMNS=100 exa’ with file arguments produces a grid of 3 columns, with full paths\"\nshell = \"exa /testcases/files/*\"\nenvironment = { COLUMNS = \"100\" }\nstdout = { file = \"outputs/files_paths_grid_3col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=150 exa’ with file arguments produces a grid of 5 columns, with full paths\"\nshell = \"exa /testcases/files/*\"\nenvironment = { COLUMNS = \"150\" }\nstdout = { file = \"outputs/files_paths_grid_5col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n\n[[cmd]]\nname = \"‘COLUMNS=200 exa’ with file arguments produces a grid of 7 columns, with full paths\"\nshell = \"exa /testcases/files/*\"\nenvironment = { COLUMNS = \"200\" }\nstdout = { file = \"outputs/files_paths_grid_7col.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid' ]\n"
  },
  {
    "path": "xtests/help.toml",
    "content": "[[cmd]]\nname = \"‘exa --help’ produces the correct help text\"\nshell = \"exa --help\"\nstdout = { file = \"outputs/help.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'help' ]\n"
  },
  {
    "path": "xtests/icons.toml",
    "content": "# view icons tests\n\n[[cmd]]\nname = \"‘exa -1 --icons’ shows icons next to file names in lines mode\"\nshell = \"exa -1 --icons /testcases/files\"\nstdout = { file = \"outputs/files_oneline_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'icons' ]\n\n[[cmd]]\nname = \"‘exa --icons’ shows icons next to file names in grid mode\"\nshell = \"exa --icons /testcases/files\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/files_grid_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'grid', 'icons' ]\n\n[[cmd]]\nname = \"‘exa -l --icons’ shows icons next to file names in long mode\"\nshell = \"exa -l --icons /testcases/files\"\nstdout = { file = \"outputs/files_long_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'icons' ]\n\n[[cmd]]\nname = \"‘exa -lG --icons’ shows icons next to file names in long-grid mode\"\nshell = \"exa -lG --icons /testcases/files\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/files_long_grid_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'long', 'grid', 'icons' ]\n\n[[cmd]]\nname = \"‘exa -T --icons’ shows icons next to file names in tree mode\"\nshell = \"exa -T --icons /testcases/files\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/files_tree_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree', 'icons' ]\n\n[[cmd]]\nname = \"‘exa -lT --icons’ shows icons next to file names in long-tree mode\"\nshell = \"exa -lT --icons /testcases/files\"\nstdout = { file = \"outputs/files_long_tree_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'tree', 'icons' ]\n\n\n# file type icons tests\n\n[[cmd]]\nname = \"‘exa -1 --icons’ produces icons based on file types\"\nshell = \"exa -1 --icons /testcases/file-names-exts\"\nstdout = { file = \"outputs/exts_oneline_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'icons' ]\n\n[[cmd]]\nname = \"‘exa -1 --icons’ produces icons based on permissions\"\nshell = \"exa -1 --icons /testcases/permissions\"\nstdout = { file = \"outputs/permissions_oneline_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'icons' ]\n\n[[cmd]]\nname = \"‘exa -1 --icons’ produces icons for links\"\nshell = \"exa -1 --icons /testcases/links\"\nstdout = { file = \"outputs/links_oneline_icons.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'icons' ]\n\n\n# icon spacing tests\n\n[[cmd]]\nname = \"‘EXA_ICON_SPACING=0 exa -1 --icons’ puts no spaces between icons and file names\"\nshell = \"EXA_ICON_SPACING=0 exa -1 --icons /testcases/links\"\nstdout = { file = \"outputs/links_oneline_icons_width0.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'oneline', 'icons' ]\n\n[[cmd]]\nname = \"‘EXA_ICON_SPACING=1 exa -1 --icons’ puts one space between icons and file names\"\nshell = \"EXA_ICON_SPACING=1 exa -1 --icons /testcases/links\"\nstdout = { file = \"outputs/links_oneline_icons.ansitxt\" }  # same as the default\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'oneline', 'icons' ]\n\n[[cmd]]\nname = \"‘EXA_ICON_SPACING=2 exa -1 --icons’ puts two spaces between icons and file names\"\nshell = \"EXA_ICON_SPACING=2 exa -1 --icons /testcases/links\"\nstdout = { file = \"outputs/links_oneline_icons_width2.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'oneline', 'icons' ]\n\n[[cmd]]\nname = \"‘EXA_ICON_SPACING=3 exa -1 --icons’ puts three spaces between icons and file names\"\nshell = \"EXA_ICON_SPACING=3 exa -1 --icons /testcases/links\"\nstdout = { file = \"outputs/links_oneline_icons_width3.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'env', 'oneline', 'icons' ]\n"
  },
  {
    "path": "xtests/ignore-glob.toml",
    "content": "[[cmd]]\nname = \"‘exa -1 -I’ ignores based on a glob\"\nshell = \"exa -1 -I '*.OGG' /testcases/file-names-exts/music.*\"\nstdout = { string = \"music.mp3\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'ignore' ]\n\n[[cmd]]\nname = \"‘exa -1 -I’ ignores based on multiple globs\"\nshell = \"exa -1 -I '*.OGG|*.mp3' /testcases/file-names-exts/music.*\"\nstdout = { empty = true }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'ignore' ]\n"
  },
  {
    "path": "xtests/input-options.toml",
    "content": "[[cmd]]\nname = \"exa can handle invalid UTF-8 in command-line arguments\"\nshell = \"exa /testcases/file-names/*\"\nstdout = { empty = false }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'options' ]\n\n[[cmd]]\nname = \"exa displays an error for an unknown short option\"\nshell = \"exa -4\"\nstdout = { empty = true }\nstderr = { string = \"Unknown argument -4\" }\nstatus = 3\ntags = [ 'options' ]\n\n[[cmd]]\nname = \"exa displays an error for an unknown long option\"\nshell = \"exa --ternary\"\nstdout = { empty = true }\nstderr = { string = \"Unknown argument --ternary\" }\nstatus = 3\ntags = [ 'options' ]\n\n[[cmd]]\nname = \"exa displays an error for an option missing a parameter\"\nshell = \"exa --time\"\nstdout = { empty = true }\nstderr = { string = \"Flag --time needs a value (choices: modified, changed, accessed, created)\" }\nstatus = 3\ntags = [ 'options' ]\n\n[[cmd]]\nname = \"exa displays an error for an option that cannot take a parameter has one\"\nshell = \"exa --long=time\"\nstdout = { empty = true }\nstderr = { string = \"Flag --long cannot take a value\" }\nstatus = 3\ntags = [ 'options' ]\n\n[[cmd]]\nname = \"exa displays an error for option that takes the wrong parameter\"\nshell = \"exa -l --time-style=24\"\nstdout = { empty = true }\nstderr = { string = \"Option --time-style has no \\\"24\\\" setting (choices: default, long-iso, full-iso, iso)\" }\nstatus = 3\ntags = [ 'options' ]\n\n\n# strict mode settings\n\n[[cmd]]\nname = \"exa displays a warning for a useless option in strict mode\"\nshell = \"exa --binary\"\nenvironment = { EXA_STRICT = \"1\" }\nstdout = { empty = true }\nstderr = { string = \"Option --binary (-b) is useless without option --long (-l)\" }\nstatus = 3\ntags = [ 'options' ]\n\n[[cmd]]\nname = \"exa displays a warning for a short option given twice in strict mode\"\nshell = \"exa -ll\"\nenvironment = { EXA_STRICT = \"1\" }\nstdout = { empty = true }\nstderr = { string = \"Flag -l was given twice\" }\nstatus = 3\ntags = [ 'options' ]\n\n[[cmd]]\nname = \"exa displays a warning for a short option also given as long in strict mode\"\nshell = \"exa -l --long\"\nenvironment = { EXA_STRICT = \"1\" }\nstdout = { empty = true }\nstderr = { string = \"Flag -l conflicts with flag --long\" }\nstatus = 3\ntags = [ 'options' ]\n"
  },
  {
    "path": "xtests/lines-view.toml",
    "content": "# file name tests\n\n[[cmd]]\nname = \"‘exa -1’ displays file names, one on each line\"\nshell = \"exa -1 /testcases/file-names\"\nstdout = { file = \"outputs/names_lines.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline' ]\n\n[[cmd]]\nname = \"‘exa -1d’ displays, ‘.’, ‘..’, and ‘/’ correctly\"\nshell = \"exa -1d . .. /\"\nstdout = { file = \"outputs/dirs_oneline.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'list-dirs' ]\n\n\n# symlinks tests\n\n[[cmd]]\nname = \"‘exa -1’ lists the destination of symlinks\"\nshell = \"exa -1 /testcases/links\"\nstdout = { file = \"outputs/links_lines.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline' ]\n\n[[cmd]]\nname = \"‘exa -1d’ with file arguments lists the destination of symlinks\"\nshell = \"exa -1d /testcases/links/*\"\nstdout = { file = \"outputs/links_paths_lines.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'list-dirs' ]\n"
  },
  {
    "path": "xtests/outputs/attributes_files_xattrs_tree.ansitxt",
    "content": "\u001b[36m/testcases/attributes/\u001b[1;34mdirs\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xattrs_empty\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xattrs_empty_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xattrs_one-file\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m file-in-question\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xattrs_one-file_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xattrs_two-files\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m that-file\n\u001b[38;5;244m│  └──\u001b[0m this-file\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xattrs_two-files_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mone-xattr_empty\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mone-xattr_empty_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mone-xattr_one-file\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  └──\u001b[0m file-in-question\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mone-xattr_one-file_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mone-xattr_two-files\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  ├──\u001b[0m that-file\n\u001b[38;5;244m│  └──\u001b[0m this-file\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mone-xattr_two-files_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mtwo-xattrs_empty\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  └──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mtwo-xattrs_empty_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mtwo-xattrs_one-file\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  ├──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m│  └──\u001b[0m file-in-question\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mtwo-xattrs_one-file_forbidden\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mtwo-xattrs_two-files\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  ├──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m│  ├──\u001b[0m that-file\n\u001b[38;5;244m│  └──\u001b[0m this-file\n\u001b[38;5;244m└──\u001b[0m \u001b[1;34mtwo-xattrs_two-files_forbidden\u001b[0m\n\u001b[38;5;244m   └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[36m/testcases/attributes/\u001b[1;34mfiles\u001b[0m\n\u001b[38;5;244m├──\u001b[0m no-xattrs\n\u001b[38;5;244m├──\u001b[0m no-xattrs_forbidden\n\u001b[38;5;244m├──\u001b[0m one-xattr\n\u001b[38;5;244m│  └──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m├──\u001b[0m one-xattr_forbidden\n\u001b[38;5;244m├──\u001b[0m two-xattrs\n\u001b[38;5;244m│  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  └──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m└──\u001b[0m two-xattrs_forbidden\n"
  },
  {
    "path": "xtests/outputs/attributes_xattrs_long_tree.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/\u001b[1;34mattributes\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[1;34mdirs\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_empty\u001b[0m\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_empty_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_one-file\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  └──\u001b[0m file-in-question\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_one-file_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_two-files\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  ├──\u001b[0m that-file\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  └──\u001b[0m this-file\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_two-files_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m@ \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_empty\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m user.greeting (len 5)\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_empty_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m@ \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_one-file\u001b[0m\n                                     \u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  └──\u001b[0m file-in-question\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_one-file_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m@ \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_two-files\u001b[0m\n                                     \u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  ├──\u001b[0m that-file\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  └──\u001b[0m this-file\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_two-files_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m@ \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_empty\u001b[0m\n                                     \u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n                                     \u001b[38;5;244m│  │  └──\u001b[0m user.another_greeting (len 2)\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_empty_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m@ \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_one-file\u001b[0m\n                                     \u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n                                     \u001b[38;5;244m│  │  ├──\u001b[0m user.another_greeting (len 2)\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  └──\u001b[0m file-in-question\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_one-file_forbidden\u001b[0m\n                                     \u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m@ \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_two-files\u001b[0m\n                                     \u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n                                     \u001b[38;5;244m│  │  ├──\u001b[0m user.another_greeting (len 2)\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  ├──\u001b[0m that-file\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  │  └──\u001b[0m this-file\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  └──\u001b[0m \u001b[1;34mtwo-xattrs_two-files_forbidden\u001b[0m\n                                     \u001b[38;5;244m│     └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m└──\u001b[0m \u001b[1;34mfiles\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   ├──\u001b[0m no-xattrs\n.\u001b[38;5;244m---------\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   ├──\u001b[0m no-xattrs_forbidden\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m@ \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   ├──\u001b[0m one-xattr\n                                     \u001b[38;5;244m   │  └──\u001b[0m user.greeting (len 5)\n.\u001b[38;5;244m---------\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   ├──\u001b[0m one-xattr_forbidden\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m@ \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   ├──\u001b[0m two-xattrs\n                                     \u001b[38;5;244m   │  ├──\u001b[0m user.greeting (len 5)\n                                     \u001b[38;5;244m   │  └──\u001b[0m user.another_greeting (len 2)\n.\u001b[38;5;244m---------\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   └──\u001b[0m two-xattrs_forbidden\n"
  },
  {
    "path": "xtests/outputs/attributes_xattrs_tree.ansitxt",
    "content": "\u001b[36m/testcases/\u001b[1;34mattributes\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mdirs\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_empty\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_empty_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_one-file\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m file-in-question\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_one-file_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_two-files\u001b[0m\n\u001b[38;5;244m│  │  ├──\u001b[0m that-file\n\u001b[38;5;244m│  │  └──\u001b[0m this-file\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_two-files_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_empty\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_empty_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_one-file\u001b[0m\n\u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  │  └──\u001b[0m file-in-question\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_one-file_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_two-files\u001b[0m\n\u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  │  ├──\u001b[0m that-file\n\u001b[38;5;244m│  │  └──\u001b[0m this-file\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mone-xattr_two-files_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_empty\u001b[0m\n\u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  │  └──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_empty_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_one-file\u001b[0m\n\u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  │  ├──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m│  │  └──\u001b[0m file-in-question\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_one-file_forbidden\u001b[0m\n\u001b[38;5;244m│  │  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mtwo-xattrs_two-files\u001b[0m\n\u001b[38;5;244m│  │  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m│  │  ├──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m│  │  ├──\u001b[0m that-file\n\u001b[38;5;244m│  │  └──\u001b[0m this-file\n\u001b[38;5;244m│  └──\u001b[0m \u001b[1;34mtwo-xattrs_two-files_forbidden\u001b[0m\n\u001b[38;5;244m│     └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m└──\u001b[0m \u001b[1;34mfiles\u001b[0m\n\u001b[38;5;244m   ├──\u001b[0m no-xattrs\n\u001b[38;5;244m   ├──\u001b[0m no-xattrs_forbidden\n\u001b[38;5;244m   ├──\u001b[0m one-xattr\n\u001b[38;5;244m   │  └──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m   ├──\u001b[0m one-xattr_forbidden\n\u001b[38;5;244m   ├──\u001b[0m two-xattrs\n\u001b[38;5;244m   │  ├──\u001b[0m user.greeting (len 5)\n\u001b[38;5;244m   │  └──\u001b[0m user.another_greeting (len 2)\n\u001b[38;5;244m   └──\u001b[0m two-xattrs_forbidden\n"
  },
  {
    "path": "xtests/outputs/dates_long_currentyear_localefr.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1,0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1,0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2,0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2,1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3,1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3,1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4,1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4,2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5,1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5,2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6,1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6,3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7,2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7,3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8,2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8,4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9,2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9,4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/dates_long_localefr.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m15 juin   2006\u001b[0m peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 3 mars   2003\u001b[0m pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m22 déc.   2009\u001b[0m plum\n"
  },
  {
    "path": "xtests/outputs/dates_long_localejp.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m15  6月  2006\u001b[0m peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 3  3月  2003\u001b[0m pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m22 12月  2009\u001b[0m plum\n"
  },
  {
    "path": "xtests/outputs/dates_long_time_accessed.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Accessed\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m22 Dec  2009\u001b[0m  peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m15 Jun  2006\u001b[0m  pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m 3 Mar  2003\u001b[0m  plum\n"
  },
  {
    "path": "xtests/outputs/dates_long_time_created.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Created\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m17 Oct 14:27\u001b[0m peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m17 Oct 14:27\u001b[0m pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m17 Oct 14:27\u001b[0m plum\n"
  },
  {
    "path": "xtests/outputs/dates_long_time_modified.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m15 Jun  2006\u001b[0m  peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m 3 Mar  2003\u001b[0m  pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary \u001b[34m22 Dec  2009\u001b[0m  plum\n"
  },
  {
    "path": "xtests/outputs/dates_long_timestyle_fulliso.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2006-06-15 23:14:29.000000000 +0000\u001b[0m peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2003-03-03 00:00:00.000000000 +0000\u001b[0m pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2009-12-22 10:38:53.000000000 +0000\u001b[0m plum\n"
  },
  {
    "path": "xtests/outputs/dates_long_timestyle_iso.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2006-06-15\u001b[0m peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2003-03-03\u001b[0m pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2009-12-22\u001b[0m plum\n"
  },
  {
    "path": "xtests/outputs/dates_long_timestyle_longiso.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2006-06-15 23:14\u001b[0m peach\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2003-03-03 00:00\u001b[0m pear\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2009-12-22 10:38\u001b[0m plum\n"
  },
  {
    "path": "xtests/outputs/dev_long.ansitxt",
    "content": "\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m-----\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m1\u001b[0m root \u001b[1;33mmem\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m3\u001b[0m root \u001b[1;33mnull\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m-----\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m4\u001b[0m root \u001b[1;33mport\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m5\u001b[0m root \u001b[1;33mzero\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m7\u001b[0m root \u001b[1;33mfull\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m8\u001b[0m root \u001b[1;33mrandom\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m9\u001b[0m root \u001b[1;33murandom\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/dirs_grid.ansitxt",
    "content": "\u001b[1;34m.\u001b[0m  \u001b[1;34m..\u001b[0m  \u001b[1;34m/\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/dirs_oneline.ansitxt",
    "content": "\u001b[1;34m.\u001b[0m\n\u001b[1;34m..\u001b[0m\n\u001b[1;34m/\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/error_columns_invalid.ansitxt",
    "content": "exa: Value \"abcdef\" not valid for environment variable COLUMNS: invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_columns_nines.ansitxt",
    "content": "exa: Value \"99999999999999999999999\" not valid for environment variable COLUMNS: number too large to fit in target type\n"
  },
  {
    "path": "xtests/outputs/error_grid_rows_invalid.ansitxt",
    "content": "exa: Value \"abcdef\" not valid for environment variable EXA_GRID_ROWS: invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_grid_rows_nines.ansitxt",
    "content": "exa: Value \"99999999999999999999999\" not valid for environment variable EXA_GRID_ROWS: number too large to fit in target type\n"
  },
  {
    "path": "xtests/outputs/error_icon_spacing_invalid.ansitxt",
    "content": "exa: Value \"abcdef\" not valid for environment variable EXA_ICON_SPACING: invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_icon_spacing_nines.ansitxt",
    "content": "exa: Value \"99999999999999999999999\" not valid for environment variable EXA_ICON_SPACING: number too large to fit in target type\n"
  },
  {
    "path": "xtests/outputs/error_invalid_option.ansitxt",
    "content": "exa: Unknown argument --aoeu\n"
  },
  {
    "path": "xtests/outputs/error_level_invalid.ansitxt",
    "content": "exa: Value \"abcdef\" not valid for option --level (-L): invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_level_nines.ansitxt",
    "content": "exa: Value \"99999999999999999999999\" not valid for option --level (-L): number too large to fit in target type\n"
  },
  {
    "path": "xtests/outputs/error_tree_all_all.ansitxt",
    "content": "exa: Option --tree is useless given --all --all\n"
  },
  {
    "path": "xtests/outputs/exts_compressed_paths_themed.ansitxt",
    "content": "\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.deb\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.tar.gz\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.tar.xz\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[31mcompressed.tgz\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[31mcompressed.txz\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_compressed_paths_themed_reset.ansitxt",
    "content": "\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.deb\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.tar.gz\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.tar.xz\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[0mcompressed.tgz\n\u001b[36m/testcases/file-names-exts/\u001b[0mcompressed.txz\n"
  },
  {
    "path": "xtests/outputs/exts_grid_monochrome.ansitxt",
    "content": "#SAVEFILE#       compressed.deb     crypto.asc        image.svg      VIDEO.AVI\nbackup~          compressed.tar.gz  crypto.signature  lossless.flac  video.wmv\ncompiled.class   compressed.tar.xz  document.pdf      lossless.wav   \ncompiled.coffee  compressed.tgz     DOCUMENT.XLSX     Makefile       \ncompiled.js      compressed.txz     file.tmp          music.mp3      \ncompiled.o       COMPRESSED.ZIP     IMAGE.PNG         MUSIC.OGG      \n"
  },
  {
    "path": "xtests/outputs/exts_grid_sort_name_reverse.ansitxt",
    "content": "\u001b[38;5;135mvideo.wmv\u001b[0m     \u001b[38;5;93mlossless.flac\u001b[0m  \u001b[38;5;109mcrypto.signature\u001b[0m   \u001b[31mcompressed.tar.gz\u001b[0m  \u001b[38;5;244mbackup~\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0m     \u001b[38;5;133mimage.svg\u001b[0m      \u001b[38;5;109mcrypto.asc\u001b[0m         \u001b[31mcompressed.deb\u001b[0m     \u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[38;5;92mMUSIC.OGG\u001b[0m     \u001b[38;5;133mIMAGE.PNG\u001b[0m      \u001b[31mCOMPRESSED.ZIP\u001b[0m     \u001b[38;5;137mcompiled.o\u001b[0m         \n\u001b[38;5;92mmusic.mp3\u001b[0m     \u001b[38;5;244mfile.tmp\u001b[0m       \u001b[31mcompressed.txz\u001b[0m     compiled.js        \n\u001b[1;4;33mMakefile\u001b[0m      \u001b[38;5;105mDOCUMENT.XLSX\u001b[0m  \u001b[31mcompressed.tgz\u001b[0m     compiled.coffee    \n\u001b[38;5;93mlossless.wav\u001b[0m  \u001b[38;5;105mdocument.pdf\u001b[0m   \u001b[31mcompressed.tar.xz\u001b[0m  \u001b[38;5;137mcompiled.class\u001b[0m     \n"
  },
  {
    "path": "xtests/outputs/exts_oneline_icons.ansitxt",
    "content": "\u001b[38;5;244m #SAVEFILE#\u001b[0m\n\u001b[38;5;244m backup~\u001b[0m\n\u001b[38;5;137m compiled.class\u001b[0m\n compiled.coffee\n\u001b[38;5;137m compiled.js\u001b[0m\n\u001b[38;5;137m compiled.o\u001b[0m\n\u001b[31m compressed.deb\u001b[0m\n\u001b[31m compressed.tar.gz\u001b[0m\n\u001b[31m compressed.tar.xz\u001b[0m\n\u001b[31m compressed.tgz\u001b[0m\n\u001b[31m compressed.txz\u001b[0m\n\u001b[31m COMPRESSED.ZIP\u001b[0m\n\u001b[38;5;109m crypto.asc\u001b[0m\n\u001b[38;5;109m crypto.signature\u001b[0m\n\u001b[38;5;105m document.pdf\u001b[0m\n\u001b[38;5;105m DOCUMENT.XLSX\u001b[0m\n\u001b[38;5;244m file.tmp\u001b[0m\n\u001b[38;5;133m IMAGE.PNG\u001b[0m\n\u001b[38;5;133m image.svg\u001b[0m\n\u001b[38;5;93m lossless.flac\u001b[0m\n\u001b[38;5;93m lossless.wav\u001b[0m\n\u001b[33m \u001b[1;4mMakefile\u001b[0m\n\u001b[38;5;92m music.mp3\u001b[0m\n\u001b[38;5;92m MUSIC.OGG\u001b[0m\n\u001b[38;5;135m VIDEO.AVI\u001b[0m\n\u001b[38;5;135m video.wmv\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_ext.ansitxt",
    "content": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n\u001b[38;5;109mcrypto.asc\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0m\n\u001b[38;5;137mcompiled.class\u001b[0m\ncompiled.coffee\n\u001b[31mcompressed.deb\u001b[0m\n\u001b[38;5;93mlossless.flac\u001b[0m\n\u001b[31mcompressed.tar.gz\u001b[0m\n\u001b[38;5;137mcompiled.js\u001b[0m\n\u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[38;5;137mcompiled.o\u001b[0m\n\u001b[38;5;92mMUSIC.OGG\u001b[0m\n\u001b[38;5;105mdocument.pdf\u001b[0m\n\u001b[38;5;133mIMAGE.PNG\u001b[0m\n\u001b[38;5;109mcrypto.signature\u001b[0m\n\u001b[38;5;133mimage.svg\u001b[0m\n\u001b[31mcompressed.tgz\u001b[0m\n\u001b[38;5;244mfile.tmp\u001b[0m\n\u001b[31mcompressed.txz\u001b[0m\n\u001b[38;5;93mlossless.wav\u001b[0m\n\u001b[38;5;135mvideo.wmv\u001b[0m\n\u001b[38;5;105mDOCUMENT.XLSX\u001b[0m\n\u001b[31mcompressed.tar.xz\u001b[0m\n\u001b[31mCOMPRESSED.ZIP\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_extcase.ansitxt",
    "content": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[38;5;109mcrypto.asc\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0m\n\u001b[38;5;137mcompiled.class\u001b[0m\ncompiled.coffee\n\u001b[31mcompressed.deb\u001b[0m\n\u001b[38;5;93mlossless.flac\u001b[0m\n\u001b[31mcompressed.tar.gz\u001b[0m\n\u001b[38;5;137mcompiled.js\u001b[0m\n\u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[38;5;137mcompiled.o\u001b[0m\n\u001b[38;5;92mMUSIC.OGG\u001b[0m\n\u001b[38;5;105mdocument.pdf\u001b[0m\n\u001b[38;5;133mIMAGE.PNG\u001b[0m\n\u001b[38;5;109mcrypto.signature\u001b[0m\n\u001b[38;5;133mimage.svg\u001b[0m\n\u001b[31mcompressed.tgz\u001b[0m\n\u001b[38;5;244mfile.tmp\u001b[0m\n\u001b[31mcompressed.txz\u001b[0m\n\u001b[38;5;93mlossless.wav\u001b[0m\n\u001b[38;5;135mvideo.wmv\u001b[0m\n\u001b[38;5;105mDOCUMENT.XLSX\u001b[0m\n\u001b[31mcompressed.tar.xz\u001b[0m\n\u001b[31mCOMPRESSED.ZIP\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_name.ansitxt",
    "content": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[38;5;137mcompiled.class\u001b[0m\ncompiled.coffee\n\u001b[38;5;137mcompiled.js\u001b[0m\n\u001b[38;5;137mcompiled.o\u001b[0m\n\u001b[31mcompressed.deb\u001b[0m\n\u001b[31mcompressed.tar.gz\u001b[0m\n\u001b[31mcompressed.tar.xz\u001b[0m\n\u001b[31mcompressed.tgz\u001b[0m\n\u001b[31mcompressed.txz\u001b[0m\n\u001b[31mCOMPRESSED.ZIP\u001b[0m\n\u001b[38;5;109mcrypto.asc\u001b[0m\n\u001b[38;5;109mcrypto.signature\u001b[0m\n\u001b[38;5;105mdocument.pdf\u001b[0m\n\u001b[38;5;105mDOCUMENT.XLSX\u001b[0m\n\u001b[38;5;244mfile.tmp\u001b[0m\n\u001b[38;5;133mIMAGE.PNG\u001b[0m\n\u001b[38;5;133mimage.svg\u001b[0m\n\u001b[38;5;93mlossless.flac\u001b[0m\n\u001b[38;5;93mlossless.wav\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n\u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[38;5;92mMUSIC.OGG\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0m\n\u001b[38;5;135mvideo.wmv\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_name_reverse.ansitxt",
    "content": "\u001b[38;5;135mvideo.wmv\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0m\n\u001b[38;5;92mMUSIC.OGG\u001b[0m\n\u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n\u001b[38;5;93mlossless.wav\u001b[0m\n\u001b[38;5;93mlossless.flac\u001b[0m\n\u001b[38;5;133mimage.svg\u001b[0m\n\u001b[38;5;133mIMAGE.PNG\u001b[0m\n\u001b[38;5;244mfile.tmp\u001b[0m\n\u001b[38;5;105mDOCUMENT.XLSX\u001b[0m\n\u001b[38;5;105mdocument.pdf\u001b[0m\n\u001b[38;5;109mcrypto.signature\u001b[0m\n\u001b[38;5;109mcrypto.asc\u001b[0m\n\u001b[31mCOMPRESSED.ZIP\u001b[0m\n\u001b[31mcompressed.txz\u001b[0m\n\u001b[31mcompressed.tgz\u001b[0m\n\u001b[31mcompressed.tar.xz\u001b[0m\n\u001b[31mcompressed.tar.gz\u001b[0m\n\u001b[31mcompressed.deb\u001b[0m\n\u001b[38;5;137mcompiled.o\u001b[0m\ncompiled.js\ncompiled.coffee\n\u001b[38;5;137mcompiled.class\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[38;5;244m#SAVEFILE#\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_namecase.ansitxt",
    "content": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[31mCOMPRESSED.ZIP\u001b[0m\n\u001b[38;5;105mDOCUMENT.XLSX\u001b[0m\n\u001b[38;5;133mIMAGE.PNG\u001b[0m\n\u001b[38;5;92mMUSIC.OGG\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[38;5;137mcompiled.class\u001b[0m\ncompiled.coffee\n\u001b[38;5;137mcompiled.js\u001b[0m\n\u001b[38;5;137mcompiled.o\u001b[0m\n\u001b[31mcompressed.deb\u001b[0m\n\u001b[31mcompressed.tar.gz\u001b[0m\n\u001b[31mcompressed.tar.xz\u001b[0m\n\u001b[31mcompressed.tgz\u001b[0m\n\u001b[31mcompressed.txz\u001b[0m\n\u001b[38;5;109mcrypto.asc\u001b[0m\n\u001b[38;5;109mcrypto.signature\u001b[0m\n\u001b[38;5;105mdocument.pdf\u001b[0m\n\u001b[38;5;244mfile.tmp\u001b[0m\n\u001b[38;5;133mimage.svg\u001b[0m\n\u001b[38;5;93mlossless.flac\u001b[0m\n\u001b[38;5;93mlossless.wav\u001b[0m\n\u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[38;5;135mvideo.wmv\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_themed_reset.ansitxt",
    "content": "#SAVEFILE#\nbackup~\ncompiled.class\ncompiled.coffee\ncompiled.js\ncompiled.o\ncompressed.deb\ncompressed.tar.gz\ncompressed.tar.xz\ncompressed.tgz\ncompressed.txz\nCOMPRESSED.ZIP\ncrypto.asc\ncrypto.signature\ndocument.pdf\nDOCUMENT.XLSX\nfile.tmp\nIMAGE.PNG\nimage.svg\nlossless.flac\nlossless.wav\nMakefile\nmusic.mp3\nMUSIC.OGG\nVIDEO.AVI\nvideo.wmv\n"
  },
  {
    "path": "xtests/outputs/far_dates_long.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[34m 1 Jan  2300\u001b[0m beyond-the-future\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[34m 1 Jan  1700\u001b[0m the-distant-past\n"
  },
  {
    "path": "xtests/outputs/files_grid_13col.ansitxt",
    "content": "1_bytes  2_bytes  3_bytes  4_bytes  5_bytes  6_bytes  7_bytes  8_bytes  9_bytes  10_bytes  11_bytes  12_bytes  13_bytes\n1_KiB    2_KiB    3_KiB    4_KiB    5_KiB    6_KiB    7_KiB    8_KiB    9_KiB    10_KiB    11_KiB    12_KiB    13_KiB\n1_MiB    2_MiB    3_MiB    4_MiB    5_MiB    6_MiB    7_MiB    8_MiB    9_MiB    10_MiB    11_MiB    12_MiB    13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_grid_20col.ansitxt",
    "content": "1_bytes  1_MiB    2_KiB  3_bytes  3_MiB    4_KiB  5_bytes  5_MiB    6_KiB  7_bytes  7_MiB    8_KiB  9_bytes  9_MiB     10_KiB  11_bytes  11_MiB    12_KiB  13_bytes  13_MiB\n1_KiB    2_bytes  2_MiB  3_KiB    4_bytes  4_MiB  5_KiB    6_bytes  6_MiB  7_KiB    8_bytes  8_MiB  9_KiB    10_bytes  10_MiB  11_KiB    12_bytes  12_MiB  13_KiB    \n"
  },
  {
    "path": "xtests/outputs/files_grid_4col.ansitxt",
    "content": "1_bytes  4_KiB    7_MiB     11_bytes\n1_KiB    4_MiB    8_bytes   11_KiB\n1_MiB    5_bytes  8_KiB     11_MiB\n2_bytes  5_KiB    8_MiB     12_bytes\n2_KiB    5_MiB    9_bytes   12_KiB\n2_MiB    6_bytes  9_KiB     12_MiB\n3_bytes  6_KiB    9_MiB     13_bytes\n3_KiB    6_MiB    10_bytes  13_KiB\n3_MiB    7_bytes  10_KiB    13_MiB\n4_bytes  7_KiB    10_MiB    \n"
  },
  {
    "path": "xtests/outputs/files_grid_8col.ansitxt",
    "content": "1_bytes  2_MiB    4_KiB    6_bytes  7_MiB    9_KiB     11_bytes  12_MiB\n1_KiB    3_bytes  4_MiB    6_KiB    8_bytes  9_MiB     11_KiB    13_bytes\n1_MiB    3_KiB    5_bytes  6_MiB    8_KiB    10_bytes  11_MiB    13_KiB\n2_bytes  3_MiB    5_KiB    7_bytes  8_MiB    10_KiB    12_bytes  13_MiB\n2_KiB    4_bytes  5_MiB    7_KiB    9_bytes  10_MiB    12_KiB    \n"
  },
  {
    "path": "xtests/outputs/files_grid_icons.ansitxt",
    "content": " 1_bytes   3_bytes   5_bytes   7_bytes   9_bytes    11_bytes   13_bytes\n 1_KiB     3_KiB     5_KiB     7_KiB     9_KiB      11_KiB     13_KiB\n 1_MiB     3_MiB     5_MiB     7_MiB     9_MiB      11_MiB     13_MiB\n 2_bytes   4_bytes   6_bytes   8_bytes   10_bytes   12_bytes  \n 2_KiB     4_KiB     6_KiB     8_KiB     10_KiB     12_KiB    \n 2_MiB     4_MiB     6_MiB     8_MiB     10_MiB     12_MiB    \n"
  },
  {
    "path": "xtests/outputs/files_grid_monochrome.ansitxt",
    "content": "ansi: [\\u{1b}[34mblue\\u{1b}[0m]  form-feed: [\\u{c}]      new-line-dir: [\\n]\nascii: hello                     invalid-utf8-1: [�]     new-line: [\\n]\nbackspace: [\\u{8}]               invalid-utf8-2: [�(]    return: [\\r]\nbell: [\\u{7}]                    invalid-utf8-3: [�(]    tab: [\\t]\nemoji: [🆒]                      invalid-utf8-4: [�(�(]  utf-8: pâté\nescape: [\\u{1b}]                 links                   vertical-tab: [\\u{b}]\n"
  },
  {
    "path": "xtests/outputs/files_long.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_binary.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_bytes.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m1,024\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m1,048,576\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m2,048\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m2,097,152\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m3,072\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m3,145,728\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m4,096\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m4,194,304\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m5,120\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m5,242,880\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m6,144\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m6,291,456\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m7,168\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m7,340,032\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m8,192\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m8,388,608\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m9,216\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m9,437,184\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m10,240\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m10,485,760\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m11,264\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m11,534,336\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m12,288\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m12,582,912\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m13,312\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m13,631,488\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_colourscale.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m1.0\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m1.0\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m2.0\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m2.1\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m3.1\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m3.1\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m4.1\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m4.2\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m5.1\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m5.2\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m6.1\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m6.3\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m7.2\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m7.3\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m8.2\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m8.4\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m9.2\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m9.4\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[38;5;118m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m10\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m10\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[38;5;118m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m11\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m12\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[38;5;118m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m12\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m13\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[38;5;118m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m13\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m14\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_colourscale_binary.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m1.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m1.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m2.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m2.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m3.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m3.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m4.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m4.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m5.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m5.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m6.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m6.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m7.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m7.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m8.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m8.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;190m9.0\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m9.0\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m10\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m10\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m11\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m11\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m12\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m12\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;190m13\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m13\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_colourscale_bytes.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m1,024\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m1,048,576\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m2,048\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m2,097,152\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m3,072\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m3,145,728\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m4,096\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m4,194,304\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m5,120\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m5,242,880\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m6,144\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m6,291,456\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m7,168\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m7,340,032\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m8,192\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m8,388,608\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[38;5;190m9,216\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[38;5;226m9,437,184\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[38;5;118m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;190m10,240\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m10,485,760\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[38;5;118m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;190m11,264\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m11,534,336\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[38;5;118m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;190m12,288\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m12,582,912\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m         \u001b[38;5;118m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;190m13,312\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[38;5;226m13,631,488\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_1col.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_2col.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB      \n"
  },
  {
    "path": "xtests/outputs/files_long_grid_3col.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_4col.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_MiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_bytes     .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 1_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_KiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 8_MiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 5_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_bytes     .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 2_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_KiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 9_MiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 6_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 3_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 13_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 4_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 7_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m 10_MiB      \n"
  },
  {
    "path": "xtests/outputs/files_long_grid_exa_grid_rows_2_3files.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_exa_grid_rows_5_15files.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_exa_grid_rows_6_15files.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_header_1file.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[36m/testcases/files/\u001b[0m10_bytes\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_header_2files.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m                         \u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[36m/testcases/files/\u001b[0m10_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[36m/testcases/files/\u001b[0m10_KiB\n"
  },
  {
    "path": "xtests/outputs/files_long_grid_icons.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_header.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_header_binary.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m  \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m1.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m1.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m2.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m2.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m3.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m3.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m4.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m4.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m5.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m5.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m6.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m6.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m7.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m7.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m8.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m8.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m9.0\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m9.0\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m\u001b[32mKi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m\u001b[32mMi\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_header_bytes.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m       \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m1,024\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m1,048,576\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m2,048\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m2,097,152\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m3,072\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m3,145,728\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m4,096\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m4,194,304\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m5,120\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m5,242,880\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m6,144\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m6,291,456\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m7,168\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m7,340,032\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m8,192\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m8,388,608\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m           \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m       \u001b[1;32m9,216\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m9,437,184\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m10,240\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10,485,760\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m11,264\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11,534,336\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m12,288\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12,582,912\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m      \u001b[1;32m13,312\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13,631,488\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_icons.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m  13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_monochrome.ansitxt",
    "content": ".rw-r--r--    1 cassowary  1 Jan 12:34 1_bytes\n.rw-r--r-- 1.0k cassowary  1 Jan 12:34 1_KiB\n.rw-r--r-- 1.0M cassowary  1 Jan 12:34 1_MiB\n.rw-r--r--    2 cassowary  1 Jan 12:34 2_bytes\n.rw-r--r-- 2.0k cassowary  1 Jan 12:34 2_KiB\n.rw-r--r-- 2.1M cassowary  1 Jan 12:34 2_MiB\n.rw-r--r--    3 cassowary  1 Jan 12:34 3_bytes\n.rw-r--r-- 3.1k cassowary  1 Jan 12:34 3_KiB\n.rw-r--r-- 3.1M cassowary  1 Jan 12:34 3_MiB\n.rw-r--r--    4 cassowary  1 Jan 12:34 4_bytes\n.rw-r--r-- 4.1k cassowary  1 Jan 12:34 4_KiB\n.rw-r--r-- 4.2M cassowary  1 Jan 12:34 4_MiB\n.rw-r--r--    5 cassowary  1 Jan 12:34 5_bytes\n.rw-r--r-- 5.1k cassowary  1 Jan 12:34 5_KiB\n.rw-r--r-- 5.2M cassowary  1 Jan 12:34 5_MiB\n.rw-r--r--    6 cassowary  1 Jan 12:34 6_bytes\n.rw-r--r-- 6.1k cassowary  1 Jan 12:34 6_KiB\n.rw-r--r-- 6.3M cassowary  1 Jan 12:34 6_MiB\n.rw-r--r--    7 cassowary  1 Jan 12:34 7_bytes\n.rw-r--r-- 7.2k cassowary  1 Jan 12:34 7_KiB\n.rw-r--r-- 7.3M cassowary  1 Jan 12:34 7_MiB\n.rw-r--r--    8 cassowary  1 Jan 12:34 8_bytes\n.rw-r--r-- 8.2k cassowary  1 Jan 12:34 8_KiB\n.rw-r--r-- 8.4M cassowary  1 Jan 12:34 8_MiB\n.rw-r--r--    9 cassowary  1 Jan 12:34 9_bytes\n.rw-r--r-- 9.2k cassowary  1 Jan 12:34 9_KiB\n.rw-r--r-- 9.4M cassowary  1 Jan 12:34 9_MiB\n.rw-r--r--   10 cassowary  1 Jan 12:34 10_bytes\n.rw-r--r--  10k cassowary  1 Jan 12:34 10_KiB\n.rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB\n.rw-r--r--   11 cassowary  1 Jan 12:34 11_bytes\n.rw-r--r--  11k cassowary  1 Jan 12:34 11_KiB\n.rw-r--r--  12M cassowary  1 Jan 12:34 11_MiB\n.rw-r--r--   12 cassowary  1 Jan 12:34 12_bytes\n.rw-r--r--  12k cassowary  1 Jan 12:34 12_KiB\n.rw-r--r--  13M cassowary  1 Jan 12:34 12_MiB\n.rw-r--r--   13 cassowary  1 Jan 12:34 13_bytes\n.rw-r--r--  13k cassowary  1 Jan 12:34 13_KiB\n.rw-r--r--  14M cassowary  1 Jan 12:34 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_long_tree_icons.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m    \u001b[38;5;244m-\u001b[0m \u001b[1;33mvagrant\u001b[0m   \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34m \u001b[36m/testcases/\u001b[1;34mfiles\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m  13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m└──\u001b[0m  13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_oneline_icons.ansitxt",
    "content": " 1_bytes\n 1_KiB\n 1_MiB\n 2_bytes\n 2_KiB\n 2_MiB\n 3_bytes\n 3_KiB\n 3_MiB\n 4_bytes\n 4_KiB\n 4_MiB\n 5_bytes\n 5_KiB\n 5_MiB\n 6_bytes\n 6_KiB\n 6_MiB\n 7_bytes\n 7_KiB\n 7_MiB\n 8_bytes\n 8_KiB\n 8_MiB\n 9_bytes\n 9_KiB\n 9_MiB\n 10_bytes\n 10_KiB\n 10_MiB\n 11_bytes\n 11_KiB\n 11_MiB\n 12_bytes\n 12_KiB\n 12_MiB\n 13_bytes\n 13_KiB\n 13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_paths_grid_3col.ansitxt",
    "content": "\u001b[36m/testcases/files/\u001b[0m1_bytes  \u001b[36m/testcases/files/\u001b[0m5_KiB    \u001b[36m/testcases/files/\u001b[0m9_MiB\n\u001b[36m/testcases/files/\u001b[0m1_KiB    \u001b[36m/testcases/files/\u001b[0m5_MiB    \u001b[36m/testcases/files/\u001b[0m10_bytes\n\u001b[36m/testcases/files/\u001b[0m1_MiB    \u001b[36m/testcases/files/\u001b[0m6_bytes  \u001b[36m/testcases/files/\u001b[0m10_KiB\n\u001b[36m/testcases/files/\u001b[0m2_bytes  \u001b[36m/testcases/files/\u001b[0m6_KiB    \u001b[36m/testcases/files/\u001b[0m10_MiB\n\u001b[36m/testcases/files/\u001b[0m2_KiB    \u001b[36m/testcases/files/\u001b[0m6_MiB    \u001b[36m/testcases/files/\u001b[0m11_bytes\n\u001b[36m/testcases/files/\u001b[0m2_MiB    \u001b[36m/testcases/files/\u001b[0m7_bytes  \u001b[36m/testcases/files/\u001b[0m11_KiB\n\u001b[36m/testcases/files/\u001b[0m3_bytes  \u001b[36m/testcases/files/\u001b[0m7_KiB    \u001b[36m/testcases/files/\u001b[0m11_MiB\n\u001b[36m/testcases/files/\u001b[0m3_KiB    \u001b[36m/testcases/files/\u001b[0m7_MiB    \u001b[36m/testcases/files/\u001b[0m12_bytes\n\u001b[36m/testcases/files/\u001b[0m3_MiB    \u001b[36m/testcases/files/\u001b[0m8_bytes  \u001b[36m/testcases/files/\u001b[0m12_KiB\n\u001b[36m/testcases/files/\u001b[0m4_bytes  \u001b[36m/testcases/files/\u001b[0m8_KiB    \u001b[36m/testcases/files/\u001b[0m12_MiB\n\u001b[36m/testcases/files/\u001b[0m4_KiB    \u001b[36m/testcases/files/\u001b[0m8_MiB    \u001b[36m/testcases/files/\u001b[0m13_bytes\n\u001b[36m/testcases/files/\u001b[0m4_MiB    \u001b[36m/testcases/files/\u001b[0m9_bytes  \u001b[36m/testcases/files/\u001b[0m13_KiB\n\u001b[36m/testcases/files/\u001b[0m5_bytes  \u001b[36m/testcases/files/\u001b[0m9_KiB    \u001b[36m/testcases/files/\u001b[0m13_MiB\n"
  },
  {
    "path": "xtests/outputs/files_paths_grid_5col.ansitxt",
    "content": "\u001b[36m/testcases/files/\u001b[0m1_bytes  \u001b[36m/testcases/files/\u001b[0m3_MiB    \u001b[36m/testcases/files/\u001b[0m6_KiB    \u001b[36m/testcases/files/\u001b[0m9_bytes   \u001b[36m/testcases/files/\u001b[0m11_MiB\n\u001b[36m/testcases/files/\u001b[0m1_KiB    \u001b[36m/testcases/files/\u001b[0m4_bytes  \u001b[36m/testcases/files/\u001b[0m6_MiB    \u001b[36m/testcases/files/\u001b[0m9_KiB     \u001b[36m/testcases/files/\u001b[0m12_bytes\n\u001b[36m/testcases/files/\u001b[0m1_MiB    \u001b[36m/testcases/files/\u001b[0m4_KiB    \u001b[36m/testcases/files/\u001b[0m7_bytes  \u001b[36m/testcases/files/\u001b[0m9_MiB     \u001b[36m/testcases/files/\u001b[0m12_KiB\n\u001b[36m/testcases/files/\u001b[0m2_bytes  \u001b[36m/testcases/files/\u001b[0m4_MiB    \u001b[36m/testcases/files/\u001b[0m7_KiB    \u001b[36m/testcases/files/\u001b[0m10_bytes  \u001b[36m/testcases/files/\u001b[0m12_MiB\n\u001b[36m/testcases/files/\u001b[0m2_KiB    \u001b[36m/testcases/files/\u001b[0m5_bytes  \u001b[36m/testcases/files/\u001b[0m7_MiB    \u001b[36m/testcases/files/\u001b[0m10_KiB    \u001b[36m/testcases/files/\u001b[0m13_bytes\n\u001b[36m/testcases/files/\u001b[0m2_MiB    \u001b[36m/testcases/files/\u001b[0m5_KiB    \u001b[36m/testcases/files/\u001b[0m8_bytes  \u001b[36m/testcases/files/\u001b[0m10_MiB    \u001b[36m/testcases/files/\u001b[0m13_KiB\n\u001b[36m/testcases/files/\u001b[0m3_bytes  \u001b[36m/testcases/files/\u001b[0m5_MiB    \u001b[36m/testcases/files/\u001b[0m8_KiB    \u001b[36m/testcases/files/\u001b[0m11_bytes  \u001b[36m/testcases/files/\u001b[0m13_MiB\n\u001b[36m/testcases/files/\u001b[0m3_KiB    \u001b[36m/testcases/files/\u001b[0m6_bytes  \u001b[36m/testcases/files/\u001b[0m8_MiB    \u001b[36m/testcases/files/\u001b[0m11_KiB    \n"
  },
  {
    "path": "xtests/outputs/files_paths_grid_7col.ansitxt",
    "content": "\u001b[36m/testcases/files/\u001b[0m1_bytes  \u001b[36m/testcases/files/\u001b[0m3_bytes  \u001b[36m/testcases/files/\u001b[0m5_bytes  \u001b[36m/testcases/files/\u001b[0m7_bytes  \u001b[36m/testcases/files/\u001b[0m9_bytes   \u001b[36m/testcases/files/\u001b[0m11_bytes  \u001b[36m/testcases/files/\u001b[0m13_bytes\n\u001b[36m/testcases/files/\u001b[0m1_KiB    \u001b[36m/testcases/files/\u001b[0m3_KiB    \u001b[36m/testcases/files/\u001b[0m5_KiB    \u001b[36m/testcases/files/\u001b[0m7_KiB    \u001b[36m/testcases/files/\u001b[0m9_KiB     \u001b[36m/testcases/files/\u001b[0m11_KiB    \u001b[36m/testcases/files/\u001b[0m13_KiB\n\u001b[36m/testcases/files/\u001b[0m1_MiB    \u001b[36m/testcases/files/\u001b[0m3_MiB    \u001b[36m/testcases/files/\u001b[0m5_MiB    \u001b[36m/testcases/files/\u001b[0m7_MiB    \u001b[36m/testcases/files/\u001b[0m9_MiB     \u001b[36m/testcases/files/\u001b[0m11_MiB    \u001b[36m/testcases/files/\u001b[0m13_MiB\n\u001b[36m/testcases/files/\u001b[0m2_bytes  \u001b[36m/testcases/files/\u001b[0m4_bytes  \u001b[36m/testcases/files/\u001b[0m6_bytes  \u001b[36m/testcases/files/\u001b[0m8_bytes  \u001b[36m/testcases/files/\u001b[0m10_bytes  \u001b[36m/testcases/files/\u001b[0m12_bytes  \n\u001b[36m/testcases/files/\u001b[0m2_KiB    \u001b[36m/testcases/files/\u001b[0m4_KiB    \u001b[36m/testcases/files/\u001b[0m6_KiB    \u001b[36m/testcases/files/\u001b[0m8_KiB    \u001b[36m/testcases/files/\u001b[0m10_KiB    \u001b[36m/testcases/files/\u001b[0m12_KiB    \n\u001b[36m/testcases/files/\u001b[0m2_MiB    \u001b[36m/testcases/files/\u001b[0m4_MiB    \u001b[36m/testcases/files/\u001b[0m6_MiB    \u001b[36m/testcases/files/\u001b[0m8_MiB    \u001b[36m/testcases/files/\u001b[0m10_MiB    \u001b[36m/testcases/files/\u001b[0m12_MiB    \n"
  },
  {
    "path": "xtests/outputs/files_paths_long_grid_1col.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_MiB\n"
  },
  {
    "path": "xtests/outputs/files_paths_long_grid_2col.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_bytes     .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_KiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_MiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_bytes     .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_KiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_MiB       .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_bytes     .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_KiB       \n"
  },
  {
    "path": "xtests/outputs/files_paths_long_grid_3col.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m10\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m1.0\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m6\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m10\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m10_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m2\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m11\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.0\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m6.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m6_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m11\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m2.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m2_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m7\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m12\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m11_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m3\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m12\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m7.3\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m7_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m12\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m3.1\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m3_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m8\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m13\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m12_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m4\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m13\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m8.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m8_MiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m13\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m4.2\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m4_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_bytes\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m14\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m13_MiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m5\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_bytes    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.2\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_KiB\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m1_bytes     .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m5.1\u001b[0m\u001b[32mk\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m5_KiB      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m9.4\u001b[0m\u001b[32mM\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/files/\u001b[0m9_MiB\n"
  },
  {
    "path": "xtests/outputs/files_tree_icons.ansitxt",
    "content": "\u001b[34m \u001b[36m/testcases/\u001b[1;34mfiles\u001b[0m\n\u001b[38;5;244m├──\u001b[0m  1_bytes\n\u001b[38;5;244m├──\u001b[0m  1_KiB\n\u001b[38;5;244m├──\u001b[0m  1_MiB\n\u001b[38;5;244m├──\u001b[0m  2_bytes\n\u001b[38;5;244m├──\u001b[0m  2_KiB\n\u001b[38;5;244m├──\u001b[0m  2_MiB\n\u001b[38;5;244m├──\u001b[0m  3_bytes\n\u001b[38;5;244m├──\u001b[0m  3_KiB\n\u001b[38;5;244m├──\u001b[0m  3_MiB\n\u001b[38;5;244m├──\u001b[0m  4_bytes\n\u001b[38;5;244m├──\u001b[0m  4_KiB\n\u001b[38;5;244m├──\u001b[0m  4_MiB\n\u001b[38;5;244m├──\u001b[0m  5_bytes\n\u001b[38;5;244m├──\u001b[0m  5_KiB\n\u001b[38;5;244m├──\u001b[0m  5_MiB\n\u001b[38;5;244m├──\u001b[0m  6_bytes\n\u001b[38;5;244m├──\u001b[0m  6_KiB\n\u001b[38;5;244m├──\u001b[0m  6_MiB\n\u001b[38;5;244m├──\u001b[0m  7_bytes\n\u001b[38;5;244m├──\u001b[0m  7_KiB\n\u001b[38;5;244m├──\u001b[0m  7_MiB\n\u001b[38;5;244m├──\u001b[0m  8_bytes\n\u001b[38;5;244m├──\u001b[0m  8_KiB\n\u001b[38;5;244m├──\u001b[0m  8_MiB\n\u001b[38;5;244m├──\u001b[0m  9_bytes\n\u001b[38;5;244m├──\u001b[0m  9_KiB\n\u001b[38;5;244m├──\u001b[0m  9_MiB\n\u001b[38;5;244m├──\u001b[0m  10_bytes\n\u001b[38;5;244m├──\u001b[0m  10_KiB\n\u001b[38;5;244m├──\u001b[0m  10_MiB\n\u001b[38;5;244m├──\u001b[0m  11_bytes\n\u001b[38;5;244m├──\u001b[0m  11_KiB\n\u001b[38;5;244m├──\u001b[0m  11_MiB\n\u001b[38;5;244m├──\u001b[0m  12_bytes\n\u001b[38;5;244m├──\u001b[0m  12_KiB\n\u001b[38;5;244m├──\u001b[0m  12_MiB\n\u001b[38;5;244m├──\u001b[0m  13_bytes\n\u001b[38;5;244m├──\u001b[0m  13_KiB\n\u001b[38;5;244m└──\u001b[0m  13_MiB\n"
  },
  {
    "path": "xtests/outputs/git1+2_long.ansitxt",
    "content": "/testcases/git:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mNN\u001b[0m \u001b[1;34madditions\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m \u001b[1;34medits\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[1;34mmoves\u001b[0m\n\n/testcases/git2:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mdeeply\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mignoreds\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[1;34mtarget\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git1+2_long_directories.ansitxt",
    "content": "/testcases/git/additions:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[34mM\u001b[0m edited\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m unstaged\n\n/testcases/git2/deeply:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mnested\u001b[0m\n\n/testcases/git/edits:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m both\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m15\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mM\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m unstaged\n\n/testcases/git2/deeply/nested:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mdirectory\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mrepository\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git1+2_long_nested.ansitxt",
    "content": "/testcases/git2/deeply/nested/directory:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m l8st\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m18\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m upd8d\n\n/testcases/git/edits:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m both\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m15\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mM\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m unstaged\n\n/testcases/git2/target:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m another ignored file\n\n/testcases/git2/deeply:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mnested\u001b[0m\n\n/testcases/git:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mNN\u001b[0m \u001b[1;34madditions\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m \u001b[1;34medits\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[1;34mmoves\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git1_long.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mNN\u001b[0m \u001b[1;34madditions\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m \u001b[1;34medits\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[1;34mmoves\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git1_long_additions.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[34mM\u001b[0m edited\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m unstaged\n"
  },
  {
    "path": "xtests/outputs/git1_long_edits.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m both\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m15\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mM\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m unstaged\n"
  },
  {
    "path": "xtests/outputs/git1_long_moves.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m21\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[36m/testcases/git/moves/\u001b[0mthither\n"
  },
  {
    "path": "xtests/outputs/git1_long_multiple.ansitxt",
    "content": "/testcases/git/additions:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[34mM\u001b[0m edited\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m unstaged\n\n/testcases/git/edits:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m both\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m15\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mM\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m unstaged\n"
  },
  {
    "path": "xtests/outputs/git1_long_recurse.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mNN\u001b[0m \u001b[1;34madditions\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m \u001b[1;34medits\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[1;34mmoves\u001b[0m\n\n/testcases/git/additions:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[34mM\u001b[0m edited\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m unstaged\n\n/testcases/git/edits:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m both\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m15\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mM\u001b[38;5;244m-\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m unstaged\n\n/testcases/git/moves:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m21\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m thither\n"
  },
  {
    "path": "xtests/outputs/git1_long_tree.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mNN\u001b[0m \u001b[36m/testcases/\u001b[1;34mgit\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mNN\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[1;34madditions\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[34mM\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m edited\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│  └──\u001b[0m unstaged\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[1;34medits\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m both\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m15\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mM\u001b[38;5;244m-\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m staged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m \u001b[38;5;244m│  └──\u001b[0m unstaged\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[38;5;244m└──\u001b[0m \u001b[1;34mmoves\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m21\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[38;5;244m   └──\u001b[0m thither\n"
  },
  {
    "path": "xtests/outputs/git1_paths_long_grid.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[34mM\u001b[0m \u001b[36m/testcases/git/additions/\u001b[0medited      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m15\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mM\u001b[38;5;244m-\u001b[0m \u001b[36m/testcases/git/edits/\u001b[0mstaged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[36m/testcases/git/additions/\u001b[0mstaged      .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m \u001b[36m/testcases/git/edits/\u001b[0munstaged\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[36m/testcases/git/additions/\u001b[0munstaged    .\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m21\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[32mN\u001b[38;5;244m-\u001b[0m \u001b[36m/testcases/git/moves/\u001b[0mthither\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[34mMM\u001b[0m \u001b[36m/testcases/git/edits/\u001b[0mboth            \u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m \u001b[36m/\u001b[1;34mtestcases\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_grid_gitignore.ansitxt",
    "content": "\u001b[38;5;92mmusic.m4a\u001b[0m  \u001b[1;34mnested\u001b[0m  \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_lines_gitignore.ansitxt",
    "content": "\u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[1;34mnested\u001b[0m\n\u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_gitignore.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_grid_gitignore.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m    \u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested\u001b[0m    \u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_recurse_gitignore.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested2\u001b[0m\n\n/testcases/git2/ignoreds/nested:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\n/testcases/git2/ignoreds/nested2:\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_tree_gitignore.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/git2/\u001b[1;34mignoreds\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[1;34mnested\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  └──\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m└──\u001b[0m \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_tree_gitignore.ansitxt",
    "content": "\u001b[36m/testcases/git2/\u001b[1;34mignoreds\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\u001b[38;5;244m└──\u001b[0m \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_long.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mdeeply\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mignoreds\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[1;34mtarget\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_long_ignoredcontent.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m \u001b[36m/testcases/git2/ignoreds/\u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_long_ignoreddir.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m another ignored file\n"
  },
  {
    "path": "xtests/outputs/git2_long_ignorednested.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_long_multiple.ansitxt",
    "content": "/testcases/git2/deeply:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mnested\u001b[0m\n\n/testcases/git2/ignoreds:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m \u001b[1;34mnested2\u001b[0m\n\n/testcases/git2/target:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m another ignored file\n"
  },
  {
    "path": "xtests/outputs/git2_long_nested.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m subfile\n"
  },
  {
    "path": "xtests/outputs/git2_long_recurse.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mdeeply\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mignoreds\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[1;34mtarget\u001b[0m\n\n/testcases/git2/deeply:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mnested\u001b[0m\n\n/testcases/git2/deeply/nested:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mdirectory\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mrepository\u001b[0m\n\n/testcases/git2/deeply/nested/directory:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m l8st\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m18\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m upd8d\n\n/testcases/git2/deeply/nested/repository:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m subfile\n\n/testcases/git2/ignoreds:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m \u001b[1;34mnested2\u001b[0m\n\n/testcases/git2/ignoreds/nested:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;92m70s grove.mp3\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\n/testcases/git2/ignoreds/nested2:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;92mievan polkka.mp3\u001b[0m\n\n/testcases/git2/target:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m another ignored file\n"
  },
  {
    "path": "xtests/outputs/git2_long_recurse_gitignore.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mdeeply\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mignoreds\u001b[0m\n\n/testcases/git2/deeply:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested\u001b[0m\n\n/testcases/git2/deeply/nested:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mdirectory\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mrepository\u001b[0m\n\n/testcases/git2/deeply/nested/directory:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m l8st\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m18\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m upd8d\n\n/testcases/git2/deeply/nested/repository:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m subfile\n\n/testcases/git2/ignoreds:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34mnested2\u001b[0m\n\n/testcases/git2/ignoreds/nested:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\n/testcases/git2/ignoreds/nested2:\n"
  },
  {
    "path": "xtests/outputs/git2_long_tree.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[36m/testcases/\u001b[1;34mgit2\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[1;34mdeeply\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│  └──\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│     ├──\u001b[0m \u001b[1;34mdirectory\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│     │  ├──\u001b[0m l8st\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m18\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[34mM\u001b[0m \u001b[38;5;244m│     │  └──\u001b[0m upd8d\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│     └──\u001b[0m \u001b[1;34mrepository\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m \u001b[38;5;244m│        └──\u001b[0m subfile\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[1;34mignoreds\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mnested\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;244m│  │  ├──\u001b[0m \u001b[38;5;92m70s grove.mp3\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[38;5;244m│  │  └──\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m--\u001b[0m \u001b[38;5;244m│  └──\u001b[0m \u001b[1;34mnested2\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;244m│     └──\u001b[0m \u001b[38;5;92mievan polkka.mp3\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;244m└──\u001b[0m \u001b[1;34mtarget\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[0m\u001b[2mI\u001b[0m \u001b[38;5;244m   └──\u001b[0m another ignored file\n"
  },
  {
    "path": "xtests/outputs/git2_long_tree_gitignore.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[36m/testcases/\u001b[1;34mgit2\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m├──\u001b[0m \u001b[1;34mdeeply\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│  └──\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│     ├──\u001b[0m \u001b[1;34mdirectory\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│     │  ├──\u001b[0m l8st\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m18\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│     │  └──\u001b[0m upd8d\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│     └──\u001b[0m \u001b[1;34mrepository\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m│        └──\u001b[0m subfile\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m└──\u001b[0m \u001b[1;34mignoreds\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   ├──\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   ├──\u001b[0m \u001b[1;34mnested\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   │  └──\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m   └──\u001b[0m \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_tree_gitignore.ansitxt",
    "content": "\u001b[36m/testcases/\u001b[1;34mgit2\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mdeeply\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[38;5;244m│     ├──\u001b[0m \u001b[1;34mdirectory\u001b[0m\n\u001b[38;5;244m│     │  ├──\u001b[0m l8st\n\u001b[38;5;244m│     │  └──\u001b[0m upd8d\n\u001b[38;5;244m│     └──\u001b[0m \u001b[1;34mrepository\u001b[0m\n\u001b[38;5;244m│        └──\u001b[0m subfile\n\u001b[38;5;244m└──\u001b[0m \u001b[1;34mignoreds\u001b[0m\n\u001b[38;5;244m   ├──\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[38;5;244m   ├──\u001b[0m \u001b[1;34mnested\u001b[0m\n\u001b[38;5;244m   │  └──\u001b[0m \u001b[38;5;92mfunky chicken.m4a\u001b[0m\n\u001b[38;5;244m   └──\u001b[0m \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git3_long.ansitxt",
    "content": "\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m \u001b[1;32m9\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m \u001b[36mb\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31maaa/aaa/a\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git4_long.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[38;5;244m-\u001b[32mN\u001b[0m P\u001b[31m\\u{8}\u001b[0m�UUU\n"
  },
  {
    "path": "xtests/outputs/help.ansitxt",
    "content": "Usage:\n  exa [options] [files...]\n\nMETA OPTIONS\n  -?, --help         show list of command-line options\n  -v, --version      show version of exa\n\nDISPLAY OPTIONS\n  -1, --oneline      display one entry per line\n  -l, --long         display extended file metadata as a table\n  -G, --grid         display entries as a grid (default)\n  -x, --across       sort the grid across, rather than downwards\n  -R, --recurse      recurse into directories\n  -T, --tree         recurse into directories as a tree\n  -F, --classify     display type indicator by file names\n  --colo[u]r=WHEN    when to use terminal colours (always, auto, never)\n  --colo[u]r-scale   highlight levels of file sizes distinctly\n  --icons            display icons\n  --no-icons         don't display icons (always overrides --icons)\n\nFILTERING AND SORTING OPTIONS\n  -a, --all                  show hidden and 'dot' files\n  -d, --list-dirs            list directories as files; don't list their contents\n  -L, --level DEPTH          limit the depth of recursion\n  -r, --reverse              reverse the sort order\n  -s, --sort SORT_FIELD      which field to sort by\n  --group-directories-first  list directories before other files\n  -D, --only-dirs            list only directories\n  -I, --ignore-glob GLOBS    glob patterns (pipe-separated) of files to ignore\n  --git-ignore               ignore files mentioned in '.gitignore'\n  Valid sort fields:         name, Name, extension, Extension, size, type,\n                             modified, accessed, created, inode, and none.\n                             date, time, old, and new all refer to modified.\n\nLONG VIEW OPTIONS\n  -b, --binary         list file sizes with binary prefixes\n  -B, --bytes          list file sizes in bytes, without any prefixes\n  -g, --group          list each file's group\n  -h, --header         add a header row to each column\n  -H, --links          list each file's number of hard links\n  -i, --inode          list each file's inode number\n  -m, --modified       use the modified timestamp field\n  -n, --numeric        list numeric user and group IDs\n  -S, --blocks         show number of file system blocks\n  -t, --time FIELD     which timestamp field to list (modified, accessed, created)\n  -u, --accessed       use the accessed timestamp field\n  -U, --created        use the created timestamp field\n  --changed            use the changed timestamp field\n  --time-style         how to format timestamps (default, iso, long-iso, full-iso)\n  --no-permissions     suppress the permissions field\n  --octal-permissions  list each file's permission in octal format\n  --no-filesize        suppress the filesize field\n  --no-user            suppress the user field\n  --no-time            suppress the time field\n  --git                list each file's Git status, if tracked or ignored\n  -@, --extended       list each file's extended attributes and sizes\n"
  },
  {
    "path": "xtests/outputs/hiddens_grid.ansitxt",
    "content": "visible\n"
  },
  {
    "path": "xtests/outputs/hiddens_grid_all.ansitxt",
    "content": "..extra-hidden  .hidden  visible\n"
  },
  {
    "path": "xtests/outputs/hiddens_grid_all_all.ansitxt",
    "content": "\u001b[1;34m.\u001b[0m  \u001b[1;34m..\u001b[0m  ..extra-hidden  .hidden  visible\n"
  },
  {
    "path": "xtests/outputs/hiddens_long.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m visible\n"
  },
  {
    "path": "xtests/outputs/hiddens_long_all.ansitxt",
    "content": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m ..extra-hidden\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m .hidden\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m visible\n"
  },
  {
    "path": "xtests/outputs/hiddens_long_all_all.ansitxt",
    "content": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;34m..\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m ..extra-hidden\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m .hidden\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m visible\n"
  },
  {
    "path": "xtests/outputs/links_grid.ansitxt",
    "content": "\u001b[31mbroken\u001b[0m       \u001b[31mforbidden\u001b[0m  \u001b[36mparent_dir\u001b[0m  some_file           \u001b[36msome_file_relative\u001b[0m\n\u001b[36mcurrent_dir\u001b[0m  \u001b[31mitself\u001b[0m     \u001b[36mroot\u001b[0m        \u001b[36msome_file_absolute\u001b[0m  \u001b[36musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_grid_monochrome.ansitxt",
    "content": "/testcases/file-names/links\n├── another: [\\n] -> /testcases/file-names/new-line-dir: [\\n]/another: [\\n]\n├── broken -> /testcases/file-names/new-line-dir: [\\n]/broken\n└── subfile -> /testcases/file-names/new-line-dir: [\\n]/subfile\n"
  },
  {
    "path": "xtests/outputs/links_lines.ansitxt",
    "content": "\u001b[36mbroken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36mcurrent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[36mforbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36mitself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36mparent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[36mroot\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\nsome_file\n\u001b[36msome_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36msome_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36musr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_long_classify.ansitxt",
    "content": "\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m  \u001b[1;32m7\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36mbroken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m  \u001b[1;32m1\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36mcurrent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m/\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m \u001b[1;32m12\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36mforbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m  \u001b[1;32m6\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36mitself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m  \u001b[1;32m2\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36mparent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m/\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m  \u001b[1;32m1\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36mroot\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m/\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m0\u001b[0m \u001b[1;33mvagrant\u001b[0m some_file\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m \u001b[1;32m26\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36msome_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m  \u001b[1;32m9\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36msome_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36ml\u001b[1;33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m  \u001b[1;32m4\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[36musr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m/\n"
  },
  {
    "path": "xtests/outputs/links_oneline_icons.ansitxt",
    "content": "\u001b[36m broken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36m current_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[36m forbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36m itself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36m parent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[36m root\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\n some_file\n\u001b[36m some_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36m some_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36m usr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_oneline_icons_width0.ansitxt",
    "content": "\u001b[36mbroken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36mcurrent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[36mforbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36mitself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36mparent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[36mroot\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\nsome_file\n\u001b[36msome_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36msome_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36musr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_oneline_icons_width2.ansitxt",
    "content": "\u001b[36m  broken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36m  current_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[36m  forbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36m  itself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36m  parent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[36m  root\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\n  some_file\n\u001b[36m  some_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36m  some_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36m  usr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_oneline_icons_width3.ansitxt",
    "content": "\u001b[36m   broken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36m   current_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[36m   forbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36m   itself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36m   parent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[36m   root\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\n   some_file\n\u001b[36m   some_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36m   some_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36m   usr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_oneline_sort_type.ansitxt",
    "content": "some_file\n\u001b[36mbroken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36mcurrent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[36mforbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36mitself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36mparent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[36mroot\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\n\u001b[36msome_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36msome_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36musr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_oneline_themed.ansitxt",
    "content": "\u001b[31manother: [\u001b[35m\\n\u001b[31m]\u001b[0m \u001b[33m->\u001b[0m \u001b[36m/testcases/file-names/new-line-dir: [\u001b[35m\\n\u001b[36m]/\u001b[0manother: [\u001b[35m\\n\u001b[0m]\n\u001b[31mbroken\u001b[0m \u001b[32m->\u001b[0m \u001b[1;32m/testcases/file-names/new-line-dir: [\u001b[35m\\n\u001b[32m]/broken\u001b[0m\n\u001b[31msubfile\u001b[0m \u001b[33m->\u001b[0m \u001b[36m/testcases/file-names/new-line-dir: [\u001b[35m\\n\u001b[36m]/\u001b[0msubfile\n"
  },
  {
    "path": "xtests/outputs/links_paths_lines.ansitxt",
    "content": "\u001b[36m/testcases/links/broken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[36m/testcases/links/current_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[36m/testcases/links/forbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[36m/testcases/links/itself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[36m/testcases/links/parent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[36m/testcases/links/root\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\n\u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36m/testcases/links/some_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[36m/testcases/links/some_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[36m/testcases/links/usr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_tree.ansitxt",
    "content": "\u001b[36m/testcases/\u001b[1;34mlinks\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mbroken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mcurrent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mforbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mitself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mparent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mroot\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\n\u001b[38;5;244m├──\u001b[0m some_file\n\u001b[38;5;244m├──\u001b[0m \u001b[36msome_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[38;5;244m├──\u001b[0m \u001b[36msome_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[38;5;244m└──\u001b[0m \u001b[36musr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/links_xattrs_tree.ansitxt",
    "content": "\u001b[36m/testcases/\u001b[1;34mlinks\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mbroken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mnowhere\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<No such file or directory (os error 2)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mcurrent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m.\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mforbidden\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/proc/1/root\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mitself\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31mitself\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[31m<Too many levels of symbolic links (os error 40)>\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mparent_dir\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m..\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[36mroot\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[1;34m/\u001b[0m\n\u001b[38;5;244m├──\u001b[0m some_file\n\u001b[38;5;244m├──\u001b[0m \u001b[36msome_file_absolute\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/links/\u001b[0msome_file\n\u001b[38;5;244m├──\u001b[0m \u001b[36msome_file_relative\u001b[0m \u001b[38;5;244m->\u001b[0m some_file\n\u001b[38;5;244m└──\u001b[0m \u001b[36musr\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/\u001b[1;34musr\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/names_grid.ansitxt",
    "content": "ansi: [\u001b[31m\\u{1b}\u001b[0m[34mblue\u001b[31m\\u{1b}\u001b[0m[0m]  form-feed: [\u001b[31m\\u{c}\u001b[0m]      \u001b[1;34mnew-line-dir: [\u001b[0m\u001b[31m\\n\u001b[1;34m]\u001b[0m\nascii: hello                     invalid-utf8-1: [�]     new-line: [\u001b[31m\\n\u001b[0m]\nbackspace: [\u001b[31m\\u{8}\u001b[0m]               invalid-utf8-2: [�(]    return: [\u001b[31m\\r\u001b[0m]\nbell: [\u001b[31m\\u{7}\u001b[0m]                    invalid-utf8-3: [�(]    tab: [\u001b[31m\\t\u001b[0m]\nemoji: [🆒]                      invalid-utf8-4: [�(�(]  utf-8: pâté\nescape: [\u001b[31m\\u{1b}\u001b[0m]                 \u001b[1;34mlinks\u001b[0m                   vertical-tab: [\u001b[31m\\u{b}\u001b[0m]\n"
  },
  {
    "path": "xtests/outputs/names_grid_across.ansitxt",
    "content": "ansi: [\u001b[31m\\u{1b}\u001b[0m[34mblue\u001b[31m\\u{1b}\u001b[0m[0m]  ascii: hello            backspace: [\u001b[31m\\u{8}\u001b[0m]\nbell: [\u001b[31m\\u{7}\u001b[0m]                    emoji: [🆒]             escape: [\u001b[31m\\u{1b}\u001b[0m]\nform-feed: [\u001b[31m\\u{c}\u001b[0m]               invalid-utf8-1: [�]     invalid-utf8-2: [�(]\ninvalid-utf8-3: [�(]             invalid-utf8-4: [�(�(]  \u001b[1;34mlinks\u001b[0m\n\u001b[1;34mnew-line-dir: [\u001b[0m\u001b[31m\\n\u001b[1;34m]\u001b[0m               new-line: [\u001b[31m\\n\u001b[0m]          return: [\u001b[31m\\r\u001b[0m]\ntab: [\u001b[31m\\t\u001b[0m]                        utf-8: pâté             vertical-tab: [\u001b[31m\\u{b}\u001b[0m]\n"
  },
  {
    "path": "xtests/outputs/names_grid_recurse.ansitxt",
    "content": "ansi: [\u001b[31m\\u{1b}\u001b[0m[34mblue\u001b[31m\\u{1b}\u001b[0m[0m]  form-feed: [\u001b[31m\\u{c}\u001b[0m]      \u001b[1;34mnew-line-dir: [\u001b[0m\u001b[31m\\n\u001b[1;34m]\u001b[0m\nascii: hello                     invalid-utf8-1: [�]     new-line: [\u001b[31m\\n\u001b[0m]\nbackspace: [\u001b[31m\\u{8}\u001b[0m]               invalid-utf8-2: [�(]    return: [\u001b[31m\\r\u001b[0m]\nbell: [\u001b[31m\\u{7}\u001b[0m]                    invalid-utf8-3: [�(]    tab: [\u001b[31m\\t\u001b[0m]\nemoji: [🆒]                      invalid-utf8-4: [�(�(]  utf-8: pâté\nescape: [\u001b[31m\\u{1b}\u001b[0m]                 \u001b[1;34mlinks\u001b[0m                   vertical-tab: [\u001b[31m\\u{b}\u001b[0m]\n\n/testcases/file-names/links:\n\u001b[36manother: [\u001b[31m\\n\u001b[36m]\u001b[0m  \u001b[31mbroken\u001b[0m  \u001b[36msubfile\u001b[0m\n\n/testcases/file-names/new-line-dir: [\\n]:\nanother: [\u001b[31m\\n\u001b[0m]  subfile\n"
  },
  {
    "path": "xtests/outputs/names_lines.ansitxt",
    "content": "ansi: [\u001b[31m\\u{1b}\u001b[0m[34mblue\u001b[31m\\u{1b}\u001b[0m[0m]\nascii: hello\nbackspace: [\u001b[31m\\u{8}\u001b[0m]\nbell: [\u001b[31m\\u{7}\u001b[0m]\nemoji: [🆒]\nescape: [\u001b[31m\\u{1b}\u001b[0m]\nform-feed: [\u001b[31m\\u{c}\u001b[0m]\ninvalid-utf8-1: [�]\ninvalid-utf8-2: [�(]\ninvalid-utf8-3: [�(]\ninvalid-utf8-4: [�(�(]\n\u001b[1;34mlinks\u001b[0m\n\u001b[1;34mnew-line-dir: [\u001b[0m\u001b[31m\\n\u001b[1;34m]\u001b[0m\nnew-line: [\u001b[31m\\n\u001b[0m]\nreturn: [\u001b[31m\\r\u001b[0m]\ntab: [\u001b[31m\\t\u001b[0m]\nutf-8: pâté\nvertical-tab: [\u001b[31m\\u{b}\u001b[0m]\n"
  },
  {
    "path": "xtests/outputs/names_tree.ansitxt",
    "content": "\u001b[36m/testcases/\u001b[1;34mfile-names\u001b[0m\n\u001b[38;5;244m├──\u001b[0m ansi: [\u001b[31m\\u{1b}\u001b[0m[34mblue\u001b[31m\\u{1b}\u001b[0m[0m]\n\u001b[38;5;244m├──\u001b[0m ascii: hello\n\u001b[38;5;244m├──\u001b[0m backspace: [\u001b[31m\\u{8}\u001b[0m]\n\u001b[38;5;244m├──\u001b[0m bell: [\u001b[31m\\u{7}\u001b[0m]\n\u001b[38;5;244m├──\u001b[0m emoji: [🆒]\n\u001b[38;5;244m├──\u001b[0m escape: [\u001b[31m\\u{1b}\u001b[0m]\n\u001b[38;5;244m├──\u001b[0m form-feed: [\u001b[31m\\u{c}\u001b[0m]\n\u001b[38;5;244m├──\u001b[0m invalid-utf8-1: [�]\n\u001b[38;5;244m├──\u001b[0m invalid-utf8-2: [�(]\n\u001b[38;5;244m├──\u001b[0m invalid-utf8-3: [�(]\n\u001b[38;5;244m├──\u001b[0m invalid-utf8-4: [�(�(]\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mlinks\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[36manother: [\u001b[31m\\n\u001b[36m]\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/file-names/new-line-dir: [\u001b[31m\\n\u001b[36m]/\u001b[0manother: [\u001b[31m\\n\u001b[0m]\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[36mbroken\u001b[0m \u001b[31m->\u001b[0m \u001b[4;31m/testcases/file-names/new-line-dir: [\\n]/broken\u001b[0m\n\u001b[38;5;244m│  └──\u001b[0m \u001b[36msubfile\u001b[0m \u001b[38;5;244m->\u001b[0m \u001b[36m/testcases/file-names/new-line-dir: [\u001b[31m\\n\u001b[36m]/\u001b[0msubfile\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mnew-line-dir: [\u001b[0m\u001b[31m\\n\u001b[1;34m]\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m another: [\u001b[31m\\n\u001b[0m]\n\u001b[38;5;244m│  └──\u001b[0m subfile\n\u001b[38;5;244m├──\u001b[0m new-line: [\u001b[31m\\n\u001b[0m]\n\u001b[38;5;244m├──\u001b[0m return: [\u001b[31m\\r\u001b[0m]\n\u001b[38;5;244m├──\u001b[0m tab: [\u001b[31m\\t\u001b[0m]\n\u001b[38;5;244m├──\u001b[0m utf-8: pâté\n\u001b[38;5;244m└──\u001b[0m vertical-tab: [\u001b[31m\\u{b}\u001b[0m]\n"
  },
  {
    "path": "xtests/outputs/passwd_long_group_header.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mGroup\u001b[0m     \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary 616       \u001b[34m 1 Jan 12:34\u001b[0m  unknown-gid\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m 666       cassowary \u001b[34m 1 Jan 12:34\u001b[0m  unknown-uid\n"
  },
  {
    "path": "xtests/outputs/permissions_long_group_header.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mGroup\u001b[0m     \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[38;5;244m---------\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  000\n.\u001b[38;5;244m--------\u001b[32mx\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  001\n.\u001b[38;5;244m-------\u001b[31mw\u001b[38;5;244m-\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  002\n.\u001b[38;5;244m------\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  004\n.\u001b[38;5;244m-----\u001b[32mx\u001b[38;5;244m---\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  010\n.\u001b[38;5;244m----\u001b[31mw\u001b[38;5;244m----\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  020\n.\u001b[38;5;244m---\u001b[33mr\u001b[38;5;244m-----\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  040\n.\u001b[38;5;244m--\u001b[1;4;32mx\u001b[0m\u001b[38;5;244m------\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m100\u001b[0m\n.\u001b[38;5;244m-\u001b[1;31mw\u001b[0m\u001b[38;5;244m-------\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  200\n.\u001b[1;33mr\u001b[0m\u001b[38;5;244m--------\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  400\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  644\n.\u001b[1;33mr\u001b[31mw\u001b[4;32mx\u001b[0m\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m755\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[4;32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m777\u001b[0m\n.\u001b[38;5;244m--------\u001b[35mT\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1000\n.\u001b[38;5;244m--------\u001b[35mt\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  1001\n.\u001b[38;5;244m-----\u001b[35mS\u001b[38;5;244m---\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2000\n.\u001b[38;5;244m-----\u001b[35ms\u001b[38;5;244m---\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  2010\n.\u001b[38;5;244m--\u001b[35mS\u001b[38;5;244m------\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  4000\n.\u001b[38;5;244m--\u001b[35ms\u001b[38;5;244m------\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m4100\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[35mS\u001b[33mr\u001b[31mw\u001b[35mS\u001b[33mr\u001b[31mw\u001b[35mT\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  7666\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[35ms\u001b[33mr\u001b[31mw\u001b[35ms\u001b[33mr\u001b[31mw\u001b[35mt\u001b[0m     \u001b[1;32m0\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m7777\u001b[0m\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m     \u001b[38;5;244m-\u001b[0m cassowary cassowary \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;34mforbidden-directory\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/permissions_long_group_header_sudo.ansitxt",
    "content": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mGroup\u001b[0m     \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[38;5;244m---------\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  000\n.\u001b[38;5;244m--------\u001b[32mx\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  001\n.\u001b[38;5;244m-------\u001b[31mw\u001b[38;5;244m-\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  002\n.\u001b[38;5;244m------\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  004\n.\u001b[38;5;244m-----\u001b[32mx\u001b[38;5;244m---\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  010\n.\u001b[38;5;244m----\u001b[31mw\u001b[38;5;244m----\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  020\n.\u001b[38;5;244m---\u001b[33mr\u001b[38;5;244m-----\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  040\n.\u001b[38;5;244m--\u001b[1;4;32mx\u001b[0m\u001b[38;5;244m------\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m100\u001b[0m\n.\u001b[38;5;244m-\u001b[1;31mw\u001b[0m\u001b[38;5;244m-------\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  200\n.\u001b[1;33mr\u001b[0m\u001b[38;5;244m--------\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  400\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  644\n.\u001b[1;33mr\u001b[31mw\u001b[4;32mx\u001b[0m\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m755\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[4;32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m777\u001b[0m\n.\u001b[38;5;244m--------\u001b[35mT\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  1000\n.\u001b[38;5;244m--------\u001b[35mt\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  1001\n.\u001b[38;5;244m-----\u001b[35mS\u001b[38;5;244m---\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  2000\n.\u001b[38;5;244m-----\u001b[35ms\u001b[38;5;244m---\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  2010\n.\u001b[38;5;244m--\u001b[35mS\u001b[38;5;244m------\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  4000\n.\u001b[38;5;244m--\u001b[35ms\u001b[38;5;244m------\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m4100\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[35mS\u001b[33mr\u001b[31mw\u001b[35mS\u001b[33mr\u001b[31mw\u001b[35mT\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  7666\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[35ms\u001b[33mr\u001b[31mw\u001b[35ms\u001b[33mr\u001b[31mw\u001b[35mt\u001b[0m     \u001b[1;32m0\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;32m7777\u001b[0m\n\u001b[1;34md\u001b[0m\u001b[38;5;244m---------\u001b[0m     \u001b[38;5;244m-\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[1;33mcassowary\u001b[0m \u001b[34m 1 Jan 12:34\u001b[0m  \u001b[1;34mforbidden-directory\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/permissions_long_themed.ansitxt",
    "content": "\u001b[38;5;250m.\u001b[38;5;237m---------\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m000\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m--------\u001b[38;5;121mx\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m001\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m-------\u001b[38;5;120mw\u001b[38;5;237m-\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m002\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m------\u001b[38;5;119mr\u001b[38;5;237m--\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m004\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m-----\u001b[38;5;118mx\u001b[38;5;237m---\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m010\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m----\u001b[38;5;190mw\u001b[38;5;237m----\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m020\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m---\u001b[38;5;191mr\u001b[38;5;237m-----\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m040\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m--\u001b[38;5;192mx\u001b[38;5;237m------\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;48m100\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m-\u001b[38;5;193mw\u001b[38;5;237m-------\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m200\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;194mr\u001b[38;5;237m--------\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m400\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;194mr\u001b[38;5;193mw\u001b[38;5;237m-\u001b[38;5;191mr\u001b[38;5;237m--\u001b[38;5;119mr\u001b[38;5;237m--\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m644\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;194mr\u001b[38;5;193mw\u001b[38;5;192mx\u001b[38;5;191mr\u001b[38;5;237m-\u001b[38;5;118mx\u001b[38;5;119mr\u001b[38;5;237m-\u001b[38;5;121mx\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;48m755\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;194mr\u001b[38;5;193mw\u001b[38;5;192mx\u001b[38;5;191mr\u001b[38;5;190mw\u001b[38;5;118mx\u001b[38;5;119mr\u001b[38;5;120mw\u001b[38;5;121mx\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;48m777\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m--------\u001b[38;5;50mT\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m1000\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m--------\u001b[38;5;50mt\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m1001\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m-----\u001b[38;5;50mS\u001b[38;5;237m---\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m2000\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m-----\u001b[38;5;50ms\u001b[38;5;237m---\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m2010\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m--\u001b[38;5;50mS\u001b[38;5;237m------\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m4000\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;237m--\u001b[38;5;51ms\u001b[38;5;237m------\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;48m4100\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;194mr\u001b[38;5;193mw\u001b[38;5;50mS\u001b[38;5;191mr\u001b[38;5;190mw\u001b[38;5;50mS\u001b[38;5;119mr\u001b[38;5;120mw\u001b[38;5;50mT\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;250m7666\u001b[0m\n\u001b[38;5;250m.\u001b[38;5;194mr\u001b[38;5;193mw\u001b[38;5;51ms\u001b[38;5;191mr\u001b[38;5;190mw\u001b[38;5;50ms\u001b[38;5;119mr\u001b[38;5;120mw\u001b[38;5;50mt\u001b[0m \u001b[38;5;49m0\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;48m7777\u001b[0m\n\u001b[38;5;195md\u001b[38;5;237m---------\u001b[0m \u001b[38;5;237m-\u001b[0m \u001b[38;5;46mcassowary\u001b[0m \u001b[38;5;47m 1 Jan 12:34\u001b[0m \u001b[38;5;195mforbidden-directory\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/permissions_oneline_icons.ansitxt",
    "content": " 000\n 001\n 002\n 004\n 010\n 020\n 040\n\u001b[32m \u001b[1m100\u001b[0m\n 200\n 400\n 644\n\u001b[32m \u001b[1m755\u001b[0m\n\u001b[32m \u001b[1m777\u001b[0m\n 1000\n 1001\n 2000\n 2010\n 4000\n\u001b[32m \u001b[1m4100\u001b[0m\n 7666\n\u001b[32m \u001b[1m7777\u001b[0m\n\u001b[34m \u001b[1mforbidden-directory\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/proc_1_root.ansitxt",
    "content": "\u001b[36m/proc/1/root\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/proc_1_root_xattrs.ansitxt",
    "content": "\u001b[36m/proc/1/root\u001b[0m\n\u001b[38;5;244m└──\u001b[0m \u001b[31m<Permission denied (os error 13)>\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/specials_long.ansitxt",
    "content": "\u001b[1;33mbr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m3\u001b[0m\u001b[38;5;244m,\u001b[32m60\u001b[0m root \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;33mblock-device\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m14\u001b[0m\u001b[38;5;244m,\u001b[32m40\u001b[0m root \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;33mchar-device\u001b[0m\n\u001b[33m|\u001b[1mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m root \u001b[34m 1 Jan 12:34\u001b[0m \u001b[33mnamed-pipe\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/specials_long_classify.ansitxt",
    "content": "\u001b[1;33mbr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m3\u001b[0m\u001b[38;5;244m,\u001b[32m60\u001b[0m root \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;33mblock-device\u001b[0m\n\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m14\u001b[0m\u001b[38;5;244m,\u001b[32m40\u001b[0m root \u001b[34m 1 Jan 12:34\u001b[0m \u001b[1;33mchar-device\u001b[0m\n\u001b[33m|\u001b[1mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m0\u001b[0m root \u001b[34m 1 Jan 12:34\u001b[0m \u001b[33mnamed-pipe\u001b[0m|\n"
  },
  {
    "path": "xtests/outputs/specials_oneline_sort_type.ansitxt",
    "content": "\u001b[33mnamed-pipe\u001b[0m\n\u001b[1;33mchar-device\u001b[0m\n\u001b[1;33mblock-device\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/specials_oneline_themed.ansitxt",
    "content": "\u001b[31mblock-device\u001b[0m\n\u001b[32mchar-device\u001b[0m\n\u001b[34mnamed-pipe\u001b[0m\n"
  },
  {
    "path": "xtests/run.sh",
    "content": "#!/bin/bash\ntrap 'exit' ERR\n\n# Check for release mode\ncase \"$1\" in\n  \"--release\") exa_binary=\"$HOME/target/release/exa\" ;;\n  *)           exa_binary=\"$HOME/target/debug/exa\" ;;\nesac\n\nif [ ! -e /vagrant ]; then\n  echo \"The extended tests must be run on the Vagrant machine.\"\n  exit 1\nfi\n\nif [ ! -f \"$exa_binary\" ]; then\n  echo \"exa binary ($exa_binary) does not exist\"\n  if [ \"$1\" != \"--release\" ]; then echo -e \"create it first with \\033[1;32mbuild-exa\\033[0m or \\033[1;32mb\\033[0m\"; fi\n  exit 1\nfi\n\necho -e \"#!/bin/sh\\nexec $exa_binary --colour=always \\\"\\$@\\\"\" > /tmp/exa\nchmod +x /tmp/exa\nexport PATH=\"/tmp:$PATH\"\n\n# Unset any environment variables\nexport EXA_STRICT=\"\"\nexport EXA_DEBUG=\"\"\nexport LS_COLORS=\"\"\nexport EXA_COLORS=\"\"\n\n# Run the tests\nexec specsheet $(dirname \"$0\")/*.toml -O cmd.shell=bash\n"
  },
  {
    "path": "xtests/sorting.toml",
    "content": "# sorting by name\n\n[[cmd]]\nname = \"‘exa -1 --sort=name’ sorts by file name\"\nshell = \"exa -1 --sort=name /testcases/file-names-exts\"\nstdout = { file = \"outputs/exts_oneline_sort_name.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=Name’ sorts by file name (case-sensitively)\"\nshell = \"exa -1 --sort=Name /testcases/file-names-exts\"\nstdout = { file = \"outputs/exts_oneline_sort_namecase.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort' ]\n\n\n# sorting by file extension\n\n[[cmd]]\nname = \"‘exa -1 --sort=ext’ sorts by file extension\"\nshell = \"exa -1 --sort=ext /testcases/file-names-exts\"\nstdout = { file = \"outputs/exts_oneline_sort_ext.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=Ext’ sorts by file extension (case-sensitively)\"\nshell = \"exa -1 --sort=Ext /testcases/file-names-exts\"\nstdout = { file = \"outputs/exts_oneline_sort_extcase.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort' ]\n\n\n# sorting by kind\n\n[[cmd]]\nname = \"‘exa -1 --sort=type’ sorts by file kind (files and symlinks)\"\nshell = \"exa -1 --sort=type /testcases/links\"\nstdout = { file = \"outputs/links_oneline_sort_type.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=type’ sorts by file kind (special files)\"\nshell = \"exa -1 --sort=type /testcases/specials\"\nstdout = { file = \"outputs/specials_oneline_sort_type.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort' ]\n\n\n# sorting by inode\n\n# We can’t guarantee inode numbers, but we can at least check that they’re in\n# order. The inode column is the leftmost one, so sort works for this.\n[[cmd]]\nname = \"‘exa -l --inode --sort=inode’ sorts by file inode\"\nshell = \"exa -l --inode --sort=inode /testcases/file-names-exts | sort --check\"\nstdout = { empty = true }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'long', 'inode', 'sort' ]\n\n\n# sorting by modified date\n\n[[cmd]]\nname = \"‘exa -1 --sort=modified’ sorts most recently modified at the bottom\"\nshell = \"exa -1 --sort=modified /testcases/dates\"\nstdout = { string = \"pear\\npeach\\nplum\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=modified -r’ sorts most recently modified at the top\"\nshell = \"exa -1 --sort=modified -r /testcases/dates\"\nstdout = { string = \"plum\\npeach\\npear\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates', 'reverse' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=newest’ sorts most recently modified at the bottom\"\nshell = \"exa -1 --sort=newest /testcases/dates\"\nstdout = { string = \"pear\\npeach\\nplum\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=newest -r’ sorts most recently modified at the top\"\nshell = \"exa -1 --sort=newest -r /testcases/dates\"\nstdout = { string = \"plum\\npeach\\npear\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates', 'reverse' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=oldest’ sorts most recently modified at the top\"\nshell = \"exa -1 --sort=oldest /testcases/dates\"\nstdout = { string = \"plum\\npeach\\npear\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=oldest -r’ sorts most recently modified at the bottom\"\nshell = \"exa -1 --sort=oldest -r /testcases/dates\"\nstdout = { string = \"pear\\npeach\\nplum\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates', 'reverse' ]\n\n\n# sorting by other date fields\n\n[[cmd]]\nname = \"‘exa -1 --sort=created’ sorts by created date\"\nshell = \"exa -1 --sort=created /testcases/dates\"\nstdout = { string = \"peach\\nplum\\npear\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=accessed’ sorts by accessed date\"\nshell = \"exa -1 --sort=accessed /testcases/dates\"\nstdout = { string = \"plum\\npear\\npeach\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'dates' ]\n\n# sorting with arguments specified\n\n[[cmd]]\nname = \"‘exa -G --sort=name -r’ with file arguments sorts by file name in reverse order\"\nshell = \"cd /testcases/file-names-exts; exa -G --sort=name -r *\"\nenvironment = { COLUMNS = \"80\" }\nstdout = { file = \"outputs/exts_grid_sort_name_reverse.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'grid', 'sort', 'reverse' ]\n\n[[cmd]]\nname = \"‘exa -1 --sort=name -r’ with file arguments sorts by file name in reverse order\"\nshell = \"cd /testcases/file-names-exts; exa -1 --sort=name -r *\"\nstdout = { file = \"outputs/exts_oneline_sort_name_reverse.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'sort', 'reverse' ]\n"
  },
  {
    "path": "xtests/themes.toml",
    "content": "# links\n\n[[cmd]]\nname = \"exa uses ‘EXA_COLORS’ to theme symlinks\"\nshell = \"exa -1 /testcases/file-names/links\"\nenvironment = { EXA_COLORS = \"or=32:bO=1:cc=35:ln=31:xx=33\" }\nstdout = { file = \"outputs/links_oneline_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n\n# special files\n\n[[cmd]]\nname = \"exa uses ‘LS_COLORS’ to theme pipes and devices\"\nshell = \"exa -1 /testcases/specials\"\nenvironment = { LS_COLORS = \"bd=31:cd=32:pi=34\" }\nstdout = { file = \"outputs/specials_oneline_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n[[cmd]]\nname = \"exa uses ‘EXA_COLORS’ to theme pipes and devices\"\nshell = \"exa -1 /testcases/specials\"\nenvironment = { EXA_COLORS = \"bd=31:cd=32:pi=34\" }\nstdout = { file = \"outputs/specials_oneline_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n[[cmd]]\nname = \"exa prefers ‘EXA_COLORS’ over ‘LS_COLORS’ to theme pipes and devices\"\nshell = \"exa -1 /testcases/specials\"\nenvironment = { LS_COLORS = \"bd=32:cd=34:pi=31\", EXA_COLORS = \"bd=31:cd=32:pi=34\" }\nstdout = { file = \"outputs/specials_oneline_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n\n# extensions\n\n[[cmd]]\nname = \"exa uses ‘LS_COLORS’ to theme files based on their extension\"\nshell = \"exa -1 /testcases/file-names-exts/compressed.*\"\nenvironment = { LS_COLORS = \"*.deb=1;37:*.tar.*=1;37\" }\nstdout = { file = \"outputs/exts_compressed_paths_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n[[cmd]]\nname = \"exa uses ‘EXA_COLORS’ to theme files based on their extension\"\nshell = \"exa -1 /testcases/file-names-exts/compressed.*\"\nenvironment = { LS_COLORS = \"*.deb=1;37:*.tar.*=1;37\" }\nstdout = { file = \"outputs/exts_compressed_paths_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n[[cmd]]\nname = \"exa uses both ‘LS_COLORS’ and ‘EXA_COLORS’ to theme files based on their extension\"\nshell = \"exa -1 /testcases/file-names-exts/compressed.*\"\nenvironment = { EXA_COLORS = \"*.deb=1;37\", LS_COLORS=\"*.tar.*=1;37\" }\nstdout = { file = \"outputs/exts_compressed_paths_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n[[cmd]]\nname = \"exa uses ‘EXA_COLORS’ with only ‘reset’ to not theme any files\"\nshell = \"exa -1 /testcases/file-names-exts\"\nenvironment = { EXA_COLORS = \"reset\" }\nstdout = { file = \"outputs/exts_themed_reset.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n\n# extensions with resets\n\n[[cmd]]\nname = \"exa uses ‘EXA_COLORS’ with ‘reset:’ to theme files based on their extension, and no others\"\nshell = \"exa -1 /testcases/file-names-exts/compressed.*\"\nenvironment = { EXA_COLORS = \"reset:*.deb=1;37:*.tar.*=1;37\" }\nstdout = { file = \"outputs/exts_compressed_paths_themed_reset.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n[[cmd]]\nname = \"exa ignores ‘LS_COLORS’ with ‘reset:’\"\nshell = \"exa -1 /testcases/file-names-exts/compressed.*\"\nenvironment = { LS_COLORS = \"reset:*.deb=1;37:*.tar.*=1;37\" }\nstdout = { file = \"outputs/exts_compressed_paths_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n\n\n# details view\n\n[[cmd]]\nname = \"exa uses ‘EXA_COLORS’ to theme metadata\"\nshell = \"exa --long /testcases/permissions\"\nenvironment = { EXA_COLORS = \"di=38;5;195:fi=38;5;250:xx=38;5;237:ur=38;5;194:uw=38;5;193:ux=38;5;192:gr=38;5;191:gw=38;5;190:gx=38;5;118:tr=38;5;119:tw=38;5;120:tx=38;5;121:su=38;5;51:sf=38;5;50:sn=38;5;49:un=38;5;46:da=38;5;47:ex=38;5;48\" }\nstdout = { file = \"outputs/permissions_long_themed.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'oneline', 'themes' ]\n"
  },
  {
    "path": "xtests/tree-view.toml",
    "content": "# file name tests\n\n[[cmd]]\nname = \"‘exa -T’ produces a tree of file names\"\nshell = \"exa -T /testcases/file-names\"\nstdout = { file = \"outputs/names_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree' ]\n\n\n# symlinks tests\n\n[[cmd]]\nname = \"‘exa -T’ lists the destination of symlinks\"\nshell = \"exa -T /testcases/links\"\nstdout = { file = \"outputs/links_tree.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree' ]\n\n\n# permission errors tests\n\n[[cmd]]\nname = \"‘exa -T’ displays an inaccessible directory\"\nshell = \"exa -T /proc/1/root\"\nstdout = { file = \"outputs/proc_1_root.ansitxt\" }\nstderr = { empty = true }\nstatus = 0\ntags = [ 'tree' ]\n"
  }
]