Merge branch 'main' into cafk-powertest

This commit is contained in:
Christina Sørensen 2023-09-18 07:13:34 +02:00 committed by GitHub
commit df714e52c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 1467 additions and 308 deletions

View file

@ -13,5 +13,5 @@ jobs:
uses: DeterminateSystems/flake-checker-action@v5 # This action
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v4
- name: Build default package
run: nix build
- name: Nix Flake Check
run: nix flake check --all-systems

View file

@ -28,7 +28,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [1.65.0, stable, beta, nightly]
rust: [1.70.0, stable, beta, nightly]
steps:
- name: Checkout repository
@ -40,7 +40,11 @@ jobs:
toolchain: ${{ matrix.rust }}
- name: Install cargo-hack
run: cargo install cargo-hack@0.5.27
uses: nick-fields/retry@v2
with:
timeout_minutes: 5
max_attemps: 5
command: cargo install cargo-hack
- name: Run unit tests
run: cargo hack test

15
.github/workflows/winget.yml vendored Normal file
View file

@ -0,0 +1,15 @@
name: Publish to Winget
on:
release:
types: [released]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: eza-community.eza
installers-regex: '-pc-windows-gnu\.zip$'
token: ${{ secrets.WINGET_TOKEN }}

3
.gitignore vendored
View file

@ -28,3 +28,6 @@ stage
# VHS testing stuff
out.gif
tests/tmp
## Dynamically generated
tests/test_dir

View file

@ -2,6 +2,146 @@
All notable changes to this project will be documented in this file.
## [0.12.0] - 2023-09-14
### Bug Fixes
- Expand `--all` help
- RUSTSEC-2020-0071
- Generalize gitignore to ignore all eza deb packages
- Canonicalize errors when the destination of a symbolic link is bad
- Handle other canonicalize errors in hyperlinks and git
- Fix windows build when canonicalize returns an error
- Change trycmd config to use test/itest folder for testing
- Revert to old apt install command suggestion and add hint
- Remove stray backslashes
- Is_some_and is an unstable Rust feature until 1.70
- Revert "Support for Windows Hidden Files"
- Shellcheck warnings
- Revert "Support for Windows Hidden Files"
- Shellcheck warnings
- Exit 13 on os error 13
- Rewrite comment
- Improve trace strings
- Tracing typo
### Documentation
- Expand `--all` documentation
- Add pthorpe92 gist
- Remove xtests section from readme
- Add deprecation warning to xtests/readme
- Add deprecation warning to just xtest commands
- Add deprecation warning to vagrantfile
- Add MacPorts install info
- Add gentoo
- Fix gentoo install
- Add docs for --git-repos & --git-repos-no-status
- Fix gpg armor flag for deb release in readme
- Add better explanation of git repos + no status
- Add scoop install info
- Remove color specifications. change unknown git repo status to `~`
- Fix missing color specification from man page
### Features
- Add audit workflow
- Add trycmd as dev-dependency
- Add minimal trycmd binary
- Add a few trycmd tests as example
- Document and change output for --git-repos
- Add apt installation workflow
- Adds filtering on Windows hidden files
- Adds filtering on Windows hidden files
- Adds filtering on Windows hidden files
- Added shellcheck to treefmt
- Adds filtering on Windows hidden files
- Add PERMISSION_DENIED exit code
### Miscellaneous Tasks
- Bump chrono from 0.4.27 to 0.4.30
- Removal of xtests
- Removal of vagrant
- Remove deprecated devtools
- Run spellcheck
### Refactor
- Over-engineer deb-package.sh
- Hide xtests folder
- Split trycmd into tests for all, unix and windows
- Limit unit-tests run on workflow change to unit-tests itself
- Moved generateTest.sh to devtools/
- Renamed the file
- Add tracing to various code parts
- Make std::process::exit global
### Revert
- "Support for Windows Hidden Files"
### Styling
- Remove TODO message on the absolute_path property
- Fix shellcheck issues in deb-package.sh
- Fix shellcheck issues in deb-package.sh
- Fix shellcheck issues in deb-package.sh
### Testing
- Remove vhs from flake
- Remove vhs-runner files
- Dump trycmd from nix sandbox
- Fix name of trydump
- Add trycmd
- Add nix feature
- Add example long tests for sandbox
- Set itests files to unix epoch
- Set itest files to unix epoch
- Refactor setting unix epoch
- Auto discard old definitions
- Fix test reference
- Add long_all_nix.toml
- Add long_blocksize_nix.toml
- Add long_extended_nix.toml
- Add long_git_nix.toml
- Add long_git_repos_nix.toml
- Add long_git_repos_no_status_nix.toml
- Add long_grid_nix.toml
- Add long_header_nix.toml
- Add long_icons_nix.toml
- Add long_octal_nix.toml
- Add long_time_style_relative_nix.toml
- Freeze nix tests
- Fix trydump when no files to delete
- Adding more content to test
- Modified unix and all tests
- Regenerate nix tests
- Convert windows tests with new itest dir
- Fixed windows tests being wrong
- Added a test generator
- Add more unix_tests
- Fixed unix tests to remove any distro specific
- Removed git test breaking on nix
### Build
- Add compression, checksum gen for bin
- Update flake.lock, cargo.lock
- Add deny.toml
- Remove org warnings
- Remove itest
- Update flake.lock
- Add itest, idump
- Make trycmd part of checks
### Ci
- Don't use nix feature on ci
- Enforce conventional commits
- Enforce conventional commits
## [0.11.1] - 2023-09-11
### Bug Fixes
@ -37,6 +177,8 @@ All notable changes to this project will be documented in this file.
### Miscellaneous Tasks
- Bump actions/checkout from 3 to 4
- Bump uzers to v0.11.3
- Release 0.11.1
### Testing
@ -174,6 +316,7 @@ All notable changes to this project will be documented in this file.
- Cafkafk -> eza-community
- Add gpg public key for the deb repository
- Add section about debian and ubuntu installation
- Add guidelines for commit messages
### Features

244
Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -17,6 +26,12 @@ dependencies = [
"libc",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "ansiterm"
version = "0.12.2"
@ -104,6 +119,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.79"
@ -131,6 +152,58 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "ciborium"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
[[package]]
name = "ciborium-ll"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "colorchoice"
version = "1.0.0"
@ -152,6 +225,42 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
@ -247,10 +356,11 @@ dependencies = [
[[package]]
name = "eza"
version = "0.11.1"
version = "0.12.0"
dependencies = [
"ansiterm",
"chrono",
"criterion",
"gethostname",
"git2",
"glob",
@ -332,6 +442,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.14.0"
@ -415,6 +531,32 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix 0.38.13",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "jobserver"
version = "0.1.22"
@ -561,6 +703,12 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "openssl-src"
version = "111.26.0+1.1.1u"
@ -657,6 +805,34 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "plotters"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
version = "1.0.66"
@ -736,6 +912,35 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "rustix"
version = "0.37.23"
@ -763,6 +968,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "same-file"
version = "1.0.6"
@ -804,6 +1015,17 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.3"
@ -931,6 +1153,16 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5082dc942361cdfb74eab98bf995762d6015e5bb3a20bf7c5c71213778b4fcb4"
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.2.0"
@ -1127,6 +1359,16 @@ version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "web-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -4,13 +4,13 @@ description = "A modern replacement for ls"
authors = ["Christina Sørensen <christina@cafkafk.com>"]
categories = ["command-line-utilities"]
edition = "2021"
rust-version = "1.65.0"
exclude = ["/devtools/*", "/Justfile", "/Vagrantfile", "/screenshots.png"]
rust-version = "1.70.0"
exclude = ["/devtools/*", "/Justfile", "/Vagrantfile", "/screenshots.png", "/tests"]
readme = "README.md"
homepage = "https://github.com/eza-community/eza"
license = "MIT"
repository = "https://github.com/eza-community/eza"
version = "0.11.1"
version = "0.12.0"
[package.metadata.deb]
@ -25,6 +25,7 @@ assets = [
[ "target/release/eza", "/usr/bin/eza", "0755" ],
[ "target/release/../man/eza.1", "/usr/share/man/man1/eza.1", "0644" ],
[ "target/release/../man/eza_colors.5", "/usr/share/man/man5/eza_colors.5", "0644" ],
[ "target/release/../man/eza_colors-explanation.5", "/usr/share/man/man5/eza_colors-explanation.5", "0644" ],
[ "completions/bash/eza", "/usr/share/bash-completion/completions/eza", "0644" ],
[ "completions/zsh/_eza", "/usr/share/zsh/site-functions/_eza", "0644" ],
[ "completions/fish/eza.fish", "/usr/share/fish/vendor_completions.d/eza.fish", "0644" ],
@ -71,11 +72,12 @@ uzers = "0.11.3"
chrono = { version = "0.4.30", default-features = false, features = ["clock"] }
[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }
trycmd = "0.14"
[features]
default = [ "git" ]
git = [ "git2" ]
default = ["git"]
git = ["git2"]
vendored-openssl = ["git2/vendored-openssl"]
vendored-libgit2 = ["git2/vendored-libgit2"]
# Should only be used inside of flake.nix
@ -93,4 +95,8 @@ debug = false
[profile.release]
lto = true
strip = true
opt-level = "s"
opt-level = 3
[[bench]]
name = "my_benchmark"
harness = false

135
Justfile
View file

@ -109,41 +109,134 @@ all-release: build-release test-release
sleep 10
gh pr create --draft --title "chore: release $(grep '^version' Cargo.toml | head -n 1 | grep -E '([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?' -o)" --body "This PR was auto-generated by our lovely just file" --reviewer cafkafk
# If you're not cafkafk and she isn't dead, you probably don't need to run
# this!
#----------------#
# binaries #
#----------------#
tar BINARY TARGET:
tar czvf ./target/"bin-$(convco version)"/{{BINARY}}_{{TARGET}}.tar.gz -C ./target/{{TARGET}}/release/ ./{{BINARY}}
zip BINARY TARGET:
zip -j ./target/"bin-$(convco version)"/{{BINARY}}_{{TARGET}}.zip ./target/{{TARGET}}/release/{{BINARY}}
binary BINARY TARGET:
rustup target add {{TARGET}}
cross build --release --target {{TARGET}}
just tar {{BINARY}} {{TARGET}}
just zip {{BINARY}} {{TARGET}}
checksum:
echo "# Checksums"
echo "## sha256sum"
echo '```'
sha256sum ./target/"bin-$(convco version)"/*
echo '```'
echo "## md5sum"
echo '```'
md5sum ./target/"bin-$(convco version)"/*
echo '```'
alias c := cross
# Generate release binaries for EZA
#
# usage: cross
@cross:
rustup toolchain install stable
# Setup Output Directory
mkdir -p ./target/"bin-$(convco version)"
# Build
# Install Toolchains/Targets
rustup toolchain install stable
## Linux
cross build --target x86_64-unknown-linux-gnu --release
tar czvf ./target/"bin-$(convco version)"/eza_x86_64-unknown-linux-gnu.tar.gz -C ./target/x86_64-unknown-linux-gnu/release/ ./eza
cross build --target aarch64-unknown-linux-gnu --release
tar czvf ./target/"bin-$(convco version)"/eza_aarch64-unknown-linux-gnu.tar.gz -C ./target/aarch64-unknown-linux-gnu/release/ ./eza
cross build --target arm-unknown-linux-gnueabihf --release
tar czvf ./target/"bin-$(convco version)"/arm-unknown-linux-gnueabihf.tar.gz -C ./target/arm-unknown-linux-gnueabihf/release/ ./eza
### x86
just binary eza x86_64-unknown-linux-gnu
just binary eza x86_64-unknown-linux-musl
### aarch
just binary eza aarch64-unknown-linux-gnu
### arm
just binary eza arm-unknown-linux-gnueabihf
## MacOS
# TODO: just binary eza x86_64-apple-darwin
## Windows
cross build --target x86_64-pc-windows-gnu --release
zip -j ./target/"bin-$(convco version)"/x86_64-pc-windows-gnu.zip ./target/x86_64-pc-windows-gnu/release/eza.exe
### x86
just binary eza.exe x86_64-pc-windows-gnu
# TODO: just binary eza.exe x86_64-pc-windows-gnullvm
# TODO: just binary eza.exe x86_64-pc-windows-msvc
# Generate Checksums
echo "# Checksums"
echo "## sha256sum"
echo "```"
sha256sum ./target/"bin-$(convco version)"/*
echo "```"
echo "## md5sum"
echo "```"
md5sum ./target/"bin-$(convco version)"/*
echo "```"
just checksum
#---------------------#
# Integration testing #
#---------------------#
alias gen := gen_test_dir
test_dir := "tests/test_dir"
gen_test_dir:
#!/usr/bin/env bash
rm {{test_dir}} -r;
mkdir -p {{test_dir}}
cd {{test_dir}};
# BEGIN grid
mkdir -p grid
cd grid
mkdir $(seq -w 001 1000);
seq 0001 1000 | split -l 1 -a 3 -d - file_
# Set time to unix epoch
touch --date=@0 *;
cd ..
# END grid
# BEGIN git
mkdir -p git
cd git
mkdir $(seq -w 001 10);
for f in ./*
do
cd $f
git init
seq 01 10 | split -l 1 -a 3 -d - file_
cd ..
done
cd ..
# END git
# BEGIN test_root
sudo mkdir root
sudo chmod 777 root
sudo mkdir root/empty
# END test_root
# BEGIN mknod
mkdir -p specials
sudo mknod specials/block-device b 3 60
sudo mknod specials/char-device c 14 40
sudo mknod specials/named-pipe p
# END test_root
eza -l --grid;
# Runs integration tests in nix sandbox
#
# Required nix, likely won't work on windows.

139
README.md
View file

@ -8,7 +8,8 @@ eza is a modern, maintained replacement for ls, built on [exa](https://github.co
[![Built with Nix](https://img.shields.io/badge/Built_With-Nix-5277C3.svg?logo=nixos&labelColor=73C3D5)](https://nixos.org)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
<a href="https://matrix.to/#/#eza:gitter.im"><img alt="Gitter" src="https://img.shields.io/gitter/room/eza-community/eza?logo=element&link=https%3A%2F%2Fapp.gitter.im%2F%23%2Froom%2F%23eza%3Agitter.im&link=Gitter%20matrix%20room%20for%20Eza"></a>
<a href="https://matrix.to/#/#eza-community:gitter.im"><img alt="Gitter" src="https://img.shields.io/gitter/room/eza-community/eza?logo=element&link=https%3A%2F%2Fapp.gitter.im%2F%23%2Froom%2F%23eza%3Agitter.im&link=Gitter%20matrix%20room%20for%20Eza" width=200></a>
[![Unit tests](https://github.com/eza-community/eza/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/eza-community/eza/actions/workflows/unit-tests.yml)
![Crates.io](https://img.shields.io/crates/v/eza?link=https%3A%2F%2Fcrates.io%2Fcrates%2Feza)
@ -27,20 +28,20 @@ And its **small**, **fast**, and just **one single binary**.
By deliberately making some decisions differently, eza attempts to be a more featureful, more user-friendly version of `ls`.
---
**eza** features not in exa (non-exhaustive):
- Fixes [“The Grid Bug”](https://github.com/eza-community/eza/issues/66#issuecomment-1656758327) introduced in exa 2021.
- Hyperlink support.
- Mount point details.
- Selinux context output.
- Git repo status output.
- Human readable relative dates.
- Several security fixes.
- Many smaller bug fixes/changes!
- Support for `bright` terminal colours.
- Fixes [“The Grid Bug”](https://github.com/eza-community/eza/issues/66#issuecomment-1656758327) introduced in exa 2021.
- Hyperlink support.
- Mount point details.
- Selinux context output.
- Git repo status output.
- Human readable relative dates.
- Several security fixes.
- Support for `bright` terminal colours.
- Many smaller bug fixes/changes!
---
<a id="try-it">
@ -53,7 +54,7 @@ If you already have Nix setup with flake support, you can try out eza with the `
nix run github:eza-community/eza
Nix will build eza and run it.
Nix will build eza and run it.
If you want to pass arguments this way, use e.g. `nix run github:eza-community/eza -- -ol`.
@ -98,21 +99,24 @@ pacman -S eza
Eza is available from [deb.gierens.de](http://deb.gierens.de). The GPG public
key is in this repo under [deb.asc](/deb.asc).
To install eza from this repo use:
First make sure you have the `gpg` command, and otherwise install it via:
```bash
wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo tee /etc/apt/trusted.gpg.d/gierens.asc
echo "deb http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list
sudo apt update
sudo apt install -y gpg
```
Then install eza via:
```bash
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg
echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list
sudo chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list
sudo apt update
sudo apt install -y eza
```
**Note:** on some systems like Docker containers apt might require the key
to be in dearmored format, then use this command instead:
```bash
wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/gierens.asc
```
before proceeding with the others from above.
### Nix (Linux, MacOS)
[![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/eza.svg)](https://repology.org/project/eza/versions)
@ -154,28 +158,24 @@ The preceding repository also contains the Bash, Fish, and Zsh completions.
### Fedora
You can install Eza from [openSUSE:Factory/eza](https://build.opensuse.org/package/show/openSUSE:Factory/eza):
Fedora support is in the works.
https://bugzilla.redhat.com/show_bug.cgi?id=2238264
### Void Linux
[![Void Linux package](https://repology.org/badge/version-for-repo/void_x86_64/eza.svg)](https://repology.org/project/eza/versions)
Eza is available as the [eza](https://github.com/void-linux/void-packages/tree/master/srcpkgs/eza) package in the official Void Linux repository.
```bash
tee /etc/yum.repos.d/opensuse-tumbleweed-oss.repo <<EOL
[opensuse-tumbleweed-oss]
name=OpenSUSE Tumbleweed OSS
baseurl=https://download.opensuse.org/tumbleweed/repo/oss/
enabled=1
gpgcheck=1
gpgkey=https://download.opensuse.org/tumbleweed/repo/oss/repodata/repomd.xml.key
EOL
dnf install eza
sudo xbps-install eza
```
The preceding repository also contains the Bash, Fish, and Zsh completions.
### Brew (MacOS)
[![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/eza.svg)](https://repology.org/project/eza/versions)
Eza is available from [Homebrew](https://formulae.brew.sh/formula/eza#default).
To install eza, run:
@ -196,11 +196,23 @@ To install eza, run:
sudo port install eza
```
### Winget (Windows)
[![Windows package](https://repology.org/badge/version-for-repo/winget/eza.svg)](https://repology.org/project/eza/versions)
Eza is available on Winget.
To install eza, run:
```shell
winget install eza-community.eza
```
### Scoop (Windows)
[![Windows package](https://repology.org/badge/version-for-repo/scoop/eza.svg)](https://repology.org/project/eza/versions)
Eza is available from [Scoop](https://scoop.sh/#/apps?q=eza&id=a52070d25f94bbcc884f80bef53eb47ed1268198).
To install eza, run:
@ -209,7 +221,35 @@ To install eza, run:
scoop install eza
```
### Completions
#### For zsh:
> **Note**
> Change `~/.zshrc` to your preferred zsh config file.
##### Clone the repository:
```sh
git clone https://github.com/eza-community/eza.git
```
##### Add the completion path to your zsh configuration:
Replace `<path_to_eza>` with the actual path where you cloned the `eza` repository.
```sh
echo 'export FPATH="<path_to_eza>/completions/zsh:$FPATH"' >> ~/.zshrc
```
##### Reload your zsh configuration:
```sh
source ~/.zshrc
```
---
Click sections to expand.
<a id="options">
@ -294,13 +334,10 @@ Some of the options accept parameters:
<summary> Development </summary>
<h1>Development
<a href="https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html">
<img src="https://img.shields.io/badge/rustc-1.63.0+-lightgray.svg" alt="Rust 1.63.0+" />
<a href="https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html">
<img src="https://img.shields.io/badge/rustc-1.70.0+-lightgray.svg" alt="Rust 1.70.0" />
</a>
<a href="https://github.com/eza-community/eza/blob/master/LICENCE">
<img src="https://img.shields.io/badge/licence-MIT-green" alt="MIT Licence" />
</a>
</h1></a>
eza is written in [Rust](https://www.rust-lang.org/).
@ -313,22 +350,22 @@ Once Rust is installed, you can compile eza with Cargo:
cargo test
- The [just](https://github.com/casey/just) command runner can be used to run some helpful development commands, in a manner similar to `make`.
Run `just --list` to get an overview of whats available.
Run `just --list` to get an overview of whats available.
- 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.
Copy the resulting binary, which will be in the `target/release` directory, into a folder in your `$PATH`.
`/usr/local/bin` is usually a good choice.
Copy the resulting binary, which will be in the `target/release` directory, into a folder in your `$PATH`.
`/usr/local/bin` is usually a good choice.
- To compile and install the manual pages, you will need [pandoc](https://pandoc.org/).
The `just man` command will compile the Markdown into manual pages, which it will place in the `target/man` directory.
To use them, copy them into a directory that `man` will read.
`/usr/local/share/man` is usually a good choice.
The `just man` command will compile the Markdown into manual pages, which it will place in the `target/man` directory.
To use them, copy them into a directory that `man` will read.
`/usr/local/share/man` is usually a good choice.
- eza depends on [libgit2](https://github.com/rust-lang/git2-rs) for certain features.
If youre unable to compile libgit2, you can opt out of Git support by running `cargo build --no-default-features`.
If youre unable to compile libgit2, you can opt out of Git support by running `cargo build --no-default-features`.
- 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.
The full command is `cargo build --release --target=x86_64-unknown-linux-musl --features vendored-openssl,git`.
The full command is `cargo build --release --target=x86_64-unknown-linux-musl --features vendored-openssl,git`.
### Developing on Nix (experimental) ❄️
@ -337,12 +374,12 @@ If you have a working Nix installation with flake support, you can use nix to ma
nix develop
The Nix Flake has a few features:
- Run `nix flake check` to run `treefmt` on the repo.
- Run `nix build` and manually test `./results/bin/eza -- <arguments>` for easy debugging.
- Run `nix build .#test` to run `cargo test` via the flake.
- Run `nix build .#clippy` to lint with clippy (still work in progress).
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=eza-community/eza&type=Date)](https://star-history.com/#eza-community/eza&Date)

8
benches/my_benchmark.rs Normal file
View file

@ -0,0 +1,8 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("logger", |b| b.iter(|| eza::logger::configure(black_box(std::env::var_os(eza::options::vars::EXA_DEBUG)))));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View file

@ -1,5 +1,5 @@
_eza()
{
# shellcheck shell=bash
_eza() {
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
@ -9,27 +9,27 @@ _eza()
;;
--colour)
COMPREPLY=( $( compgen -W 'always auto never' -- "$cur" ) )
mapfile -t COMPREPLY < <(compgen -W 'always auto never' -- "$cur")
return
;;
-L|--level)
COMPREPLY=( $( compgen -W '{0..9}' -- "$cur" ) )
mapfile -t COMPREPLY < <(compgen -W '{0..9}' -- "$cur")
return
;;
-s|--sort)
COMPREPLY=( $( compgen -W 'name filename Name Filename size filesize extension Extension date time modified changed accessed created type inode oldest newest age none --' -- "$cur" ) )
mapfile -t COMPREPLY < <(compgen -W 'name filename Name Filename size filesize extension Extension date time modified changed accessed created type inode oldest newest age none --' -- "$cur")
return
;;
-t|--time)
COMPREPLY=( $( compgen -W 'modified changed accessed created --' -- "$cur" ) )
mapfile -t COMPREPLY < <(compgen -W 'modified changed accessed created --' -- "$cur")
return
;;
--time-style)
COMPREPLY=( $( compgen -W 'default iso long-iso full-iso relative --' -- "$cur" ) )
mapfile -t COMPREPLY < <(compgen -W 'default iso long-iso full-iso relative --' -- "$cur")
return
;;
esac
@ -38,14 +38,14 @@ _eza()
# _parse_help doesnt pick up short options when they are on the same line than long options
--*)
# colo[u]r isnt parsed correctly so we filter these options out and add them by hand
parse_help=$( eza --help | grep -oE ' (--[[:alnum:]@-]+)' | tr -d ' ' | grep -v '\--colo' )
completions=$( echo '--color --colour --color-scale --colour-scale' $parse_help )
COMPREPLY=( $( compgen -W "$completions" -- "$cur" ) )
parse_help=$(eza --help | grep -oE ' (--[[:alnum:]@-]+)' | tr -d ' ' | grep -v '\--colo')
completions=$(echo '--color --colour --color-scale --colour-scale' "$parse_help")
mapfile -t COMPREPLY < <(compgen -W "$completions" -- "$cur")
;;
-*)
completions=$( eza --help | grep -oE ' (-[[:alnum:]@])' | tr -d ' ' )
COMPREPLY=( $( compgen -W "$completions" -- "$cur" ) )
completions=$(eza --help | grep -oE ' (-[[:alnum:]@])' | tr -d ' ')
mapfile -t COMPREPLY < <(compgen -W "$completions" -- "$cur")
;;
*)

0
completions/fish/eza.fish Executable file → Normal file
View file

View file

@ -47,6 +47,9 @@ for ARCH in "${!TARGETS[@]}"; do
mkdir -p "${DEB_TMP_DIR}${DOCDIR}/man5"
mkdir -p "${DEB_TMP_DIR}/DEBIAN"
mkdir -p "${DEB_TMP_DIR}/usr/share/doc/${NAME}"
mkdir -p "${DEB_TMP_DIR}/usr/share/bash-completion/completions/"
mkdir -p "${DEB_TMP_DIR}/usr/share/fish/vendor_completions.d/"
mkdir -p "${DEB_TMP_DIR}/usr/share/zsh/vendor-completions/"
chmod 755 -R "${DEB_TMP_DIR}"
echo " -> extract executable"
@ -60,6 +63,11 @@ for ARCH in "${!TARGETS[@]}"; do
gzip -cn9 target/man/eza_colors-explanation.5 > "${DEB_TMP_DIR}${DOCDIR}man5/eza_colors-explanation.5.gz"
chmod 644 "${DEB_TMP_DIR}${DOCDIR}"/**/*.gz
echo " -> copy completions"
cp completions/bash/eza "${DEB_TMP_DIR}/usr/share/bash-completion/completions/"
cp completions/fish/eza.fish "${DEB_TMP_DIR}/usr/share/fish/vendor_completions.d/"
cp completions/zsh/_eza "${DEB_TMP_DIR}/usr/share/zsh/vendor-completions/"
echo " -> create control file"
touch "${DEB_TMP_DIR}/DEBIAN/control"
cat > "${DEB_TMP_DIR}/DEBIAN/control" <<EOM

View file

@ -0,0 +1,43 @@
#!/bin/bash
# Generate test data for the program
if [ $# -ne 2 ]; then
echo "Usage: $0 <test name> <test argument>"
exit 1
fi
# Clean up previous test data
if [ -f tests/cmd/"$1".toml ]; then
rm tests/cmd/"$1".toml
fi
if [ -f tests/cmd/"$1".stdout ]; then
rm tests/cmd/"$1".stdout
fi
if [ -f tests/cmd/"$1".stderr ]; then
rm tests/cmd/"$1".stderr
fi
# Generate test data
touch tests/cmd/"$1".toml
echo 'bin.name = "eza"' >> tests/cmd/"$1".toml
echo 'args = "'"$2"'"' >> tests/cmd/"$1".toml
# Generate expected output
if [ -f target/debug/eza ]; then
target/debug/eza "$2" > tests/cmd/"$1".stdout 2> tests/cmd/"$1".stderr
returncode=$?
if [ $returncode -ne 0 ]; then
echo -e 'status.code = '$returncode'' >> tests/cmd/"$1".toml
exit 0
fi
else
echo "Please build the program first"
exit 1
fi

View file

@ -160,6 +160,7 @@
checks = {
formatting = treefmtEval.config.build.check self;
build = packages.check;
default = packages.default;
test = packages.test;
lint = packages.clippy;
trycmd = packages.trycmd;

View file

@ -150,7 +150,7 @@ These options are available when running with `--long` (`-l`):
: Use the modified timestamp field.
`-M`, `--mounts`
: Show mount details (Linux only)
: Show mount details (Linux and Mac only)
`-n`, `--numeric`
: List numeric user and group IDs.
@ -197,16 +197,16 @@ These options are available when running with `--long` (`-l`):
`--git` [if eza was built with git support]
: List each files Git status, if tracked.
This 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. :Directories 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.
`--git-repos` [if eza was built with git support]
: List each directorys Git status, if tracked.
Symbols shown are `|`= clean, `+`= dirty, and `~`= for unknown.
`--git-repos-no-status` [if eza was built with git support]
: List if a directory is a Git repository, but not its status.
All Git repository directories will be shown as (themed) `-` without status indicated.
This 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.
Directories 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.
`--no-git`
: Don't show Git status (always overrides `--git`, `--git-repos`, `--git-repos-no-status`)

View file

@ -25,9 +25,7 @@ files; setting `EXA_COLORS="reset"` will highlight nothing.
- eza now supports bright colours! As supported by most modern 256\-colour terminals, you can now choose from `bright` colour codes when selecting your custom colours in your `#EXA_COLORS` environment variable.
"Immediate" files are the files you should look at when downloading and building a project for the first time: READMEs, Makefiles, Cargo.toml, and others.
They are highlighted in _yellow_ and _underlined_.
- Build (Makefile, Cargo.toml, package.json) are yellow and underlined.
- Images (png, jpeg, gif) are purple.
- Videos (mp4, ogv, m2ts) are a slightly purpler purple.
- Music (mp3, m4a, ogg) is a deeper purple.

View file

@ -223,6 +223,36 @@ LIST OF CODES
`mp`
: a mount point
`im`
: a regular file that is an image
`vi`
: a regular file that is a video
`mu`
: a regular file that is lossy music
`lo`
: a regular file that is lossless music
`cr`
: a regular file that is related to cryptography (ex: key or certificate)
`do`
: a regular file that is a document (ex: office suite document or PDF)
`co`
: a regular file this is compressed
`tm`
: a regular file that is temporary (ex: a text editor's backup file)
`cm`
: a regular file that is a compilation artifact (ex: Java class file)
`bu`
: a regular file that is used to build a project (ex: Makefile)
Values in `EXA_COLORS` override those given in `LS_COLORS`, so you dont need to re-write an existing `LS_COLORS` variable with proprietary extensions.
@ -236,6 +266,12 @@ The codes accepted by eza are:
`1`
: for bold
`2`
: for dimmed
`3`
: for italic
`4`
: for underline

View file

@ -1,4 +1,4 @@
[toolchain]
channel = "stable"
channel = "1.70"
components = [ "rustfmt", "rustc", "rust-src", "rust-analyzer", "cargo", "clippy" ]
profile = "minimal"

View file

@ -42,6 +42,7 @@ impl Dir {
.map(|result| result.map(|entry| entry.path()))
.collect::<Result<_, _>>()?;
info!("Read directory success {:?}", &path);
Ok(Self { contents, path })
}
@ -129,8 +130,17 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
}
}
return Some(File::from_args(path.clone(), self.dir, filename, self.deref_links)
.map_err(|e| (path.clone(), e)))
let file = File::from_args(path.clone(), self.dir, filename, self.deref_links)
.map_err(|e| (path.clone(), e));
// Windows has its own concept of hidden files, when dotfiles are
// hidden Windows hidden files should also be filtered out
#[cfg(windows)]
if !self.dotfiles && file.as_ref().is_ok_and(|f| f.attributes().hidden) {
continue;
}
return Some(file);
}
return None

View file

@ -6,17 +6,18 @@ use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use chrono::prelude::*;
use log::*;
use crate::ALL_MOUNTS;
use crate::fs::dir::Dir;
use crate::fs::feature::xattr;
use crate::fs::feature::xattr::{FileAttributes, Attribute};
use crate::fs::fields as f;
use super::mounts::all_mounts;
use super::mounts::MountedFs;
@ -79,11 +80,12 @@ pub struct File<'dir> {
/// dereferencing is enabled, the size of the target will be displayed
/// instead.
pub deref_links: bool,
/// The extended attributes of this file.
pub extended_attributes: Vec<Attribute>,
extended_attributes: OnceLock<Vec<Attribute>>,
/// The absolute value of this path, used to look up mount points.
pub absolute_path: Option<PathBuf>,
absolute_path: OnceLock<Option<PathBuf>>,
}
impl<'dir> File<'dir> {
@ -98,8 +100,8 @@ impl<'dir> File<'dir> {
debug!("Statting file {:?}", &path);
let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = false;
let extended_attributes = File::gather_extended_attributes(&path);
let absolute_path = std::fs::canonicalize(&path).ok();
let extended_attributes = OnceLock::new();
let absolute_path = OnceLock::new();
Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, extended_attributes, absolute_path })
}
@ -112,8 +114,8 @@ impl<'dir> File<'dir> {
let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = true;
let parent_dir = Some(parent_dir);
let extended_attributes = File::gather_extended_attributes(&path);
let absolute_path = std::fs::canonicalize(&path).ok();
let extended_attributes = OnceLock::new();
let absolute_path = OnceLock::new();
Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false, extended_attributes, absolute_path })
}
@ -125,8 +127,8 @@ impl<'dir> File<'dir> {
let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = true;
let parent_dir = Some(parent_dir);
let extended_attributes = File::gather_extended_attributes(&path);
let absolute_path = std::fs::canonicalize(&path).ok();
let extended_attributes = OnceLock::new();
let absolute_path = OnceLock::new();
Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false, extended_attributes, absolute_path })
}
@ -176,6 +178,11 @@ impl<'dir> File<'dir> {
}
}
/// Get the extended attributes of a file path on demand.
pub fn extended_attributes(&self) -> &Vec<Attribute> {
self.extended_attributes.get_or_init(||File::gather_extended_attributes(&self.path))
}
/// Whether this file is a directory on the filesystem.
pub fn is_directory(&self) -> bool {
self.metadata.is_dir()
@ -204,6 +211,7 @@ impl<'dir> File<'dir> {
/// Returns an IO error upon failure, but this shouldnt be used to check
/// if a `File` is a directory or not! For that, just use `is_directory()`.
pub fn to_dir(&self) -> io::Result<Dir> {
trace!("to_dir: reading dir");
Dir::read_dir(self.path.clone())
}
@ -251,21 +259,22 @@ impl<'dir> File<'dir> {
self.metadata.file_type().is_socket()
}
/// Determine the full path resolving all symbolic links on demand.
pub fn absolute_path(&self) -> Option<&PathBuf> {
self.absolute_path.get_or_init(|| std::fs::canonicalize(&self.path).ok()).as_ref()
}
/// Whether this file is a mount point
pub fn is_mount_point(&self) -> bool {
if cfg!(target_os = "linux") && self.is_directory() {
return match self.absolute_path.as_ref() {
Some(path) => ALL_MOUNTS.contains_key(path),
None => false,
}
}
false
cfg!(any(target_os = "linux", target_os = "macos")) &&
self.is_directory() &&
self.absolute_path().is_some_and(|p| all_mounts().contains_key(p))
}
/// The filesystem device and type for a mount point
pub fn mount_point_info(&self) -> Option<&MountedFs> {
if cfg!(target_os = "linux") {
return self.absolute_path.as_ref().and_then(|p|ALL_MOUNTS.get(p));
if cfg!(any(target_os = "linux",target_os = "macos")) {
return self.absolute_path().and_then(|p| all_mounts().get(p));
}
None
}
@ -318,7 +327,8 @@ impl<'dir> File<'dir> {
Ok(metadata) => {
let ext = File::ext(&path);
let name = File::filename(&path);
let extended_attributes = File::gather_extended_attributes(&absolute_path);
let extended_attributes = OnceLock::new();
let absolute_path_cell = OnceLock::from(Some(absolute_path));
let file = File {
parent_dir: None,
path,
@ -328,7 +338,7 @@ impl<'dir> File<'dir> {
is_all_all: false,
deref_links: self.deref_links,
extended_attributes,
absolute_path: Some(absolute_path)
absolute_path: absolute_path_cell,
};
FileTarget::Ok(Box::new(file))
}
@ -531,6 +541,7 @@ impl<'dir> File<'dir> {
/// but as mentioned in the size function comment above, different filesystems
/// make it difficult to get any info about a dir by it's size, so this may be it.
fn is_empty_directory(&self) -> bool {
trace!("is_empty_directory: reading dir");
match Dir::read_dir(self.path.clone()) {
// . & .. are skipped, if the returned iterator has .next(), it's not empty
Ok(has_files) => has_files.files(super::DotFilter::Dotfiles, None, false, false).next().is_none(),
@ -689,7 +700,7 @@ impl<'dir> File<'dir> {
/// This files security context field.
pub fn security_context(&self) -> f::SecurityContext<'_> {
let context = match &self.extended_attributes.iter().find(|a| a.name == "security.selinux") {
let context = match self.extended_attributes().iter().find(|a| a.name == "security.selinux") {
Some(attr) => f::SecurityContextType::SELinux(&attr.value),
None => f::SecurityContextType::None
};

View file

@ -8,4 +8,4 @@ pub mod dir_action;
pub mod feature;
pub mod fields;
pub mod filter;
pub mod mounts;
pub mod mounts;

View file

@ -1,6 +0,0 @@
/// Details of a mounted filesystem.
pub struct MountedFs {
pub dest: String,
pub fstype: String,
pub source: String,
}

16
src/fs/mounts/linux.rs Normal file
View file

@ -0,0 +1,16 @@
use proc_mounts::MountList;
use crate::fs::mounts::{Error, MountedFs};
/// Get a list of all mounted filesystems
pub fn mounts() -> Result<Vec<MountedFs>, Error> {
Ok(MountList::new()
.map_err(Error::IOError)?
.0.iter()
.map(|mount| MountedFs {
dest: mount.dest.clone(),
fstype: mount.fstype.clone(),
source: mount.source.to_string_lossy().into()
})
.collect()
)
}

54
src/fs/mounts/macos.rs Normal file
View file

@ -0,0 +1,54 @@
use std::{mem, ptr};
use std::ffi::{CStr, OsStr};
use std::os::raw::{c_char, c_int};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
use libc::{__error, getfsstat, MNT_NOWAIT, statfs};
use crate::fs::mounts::{Error, MountedFs};
/// Get a list of all mounted filesystem
pub fn mounts() -> Result<Vec<MountedFs>, Error> {
// SAFETY:
// Calling external "C" function getfsstat. Passing a null pointer and zero
// bufsize will return the number of mounts.
let mut count: i32 = unsafe { getfsstat(ptr::null_mut(), 0, MNT_NOWAIT) };
let mut mntbuf = Vec::<statfs>::new();
if count > 0 {
// SAFETY: Zero out buffer memory as we allocate.
mntbuf.resize_with(count as usize, || unsafe { mem::zeroed() });
let bufsize = mntbuf.len() * mem::size_of::<statfs>();
// SAFETY:
// Calling external "C" function getfsstate with actual buffer now. The
// function takes a buffer size to not overflow. If the mount table
// changes size between calls we are protected by bufsize
count = unsafe { getfsstat(mntbuf.as_mut_ptr(), bufsize as c_int, MNT_NOWAIT) };
// Resize if the mount table has shrunk since last call
if count >= 0 {
mntbuf.truncate(count as usize);
}
}
if count < 0 {
// SAFETY: Calling external "C" errno function to get the error number
return Err(Error::GetFSStatError(unsafe { *__error() }));
}
let mut mounts = Vec::with_capacity(count as usize);
for mnt in &mntbuf {
let mount_point = OsStr::from_bytes(
// SAFETY: Converting null terminated "C" string
unsafe { CStr::from_ptr(mnt.f_mntonname.as_ptr().cast::<c_char>()) }.to_bytes()
);
let dest = PathBuf::from(mount_point);
// SAFETY: Converting null terminated "C" string
let fstype = unsafe { CStr::from_ptr(mnt.f_fstypename.as_ptr().cast::<c_char>()) }
.to_string_lossy()
.into();
// SAFETY: Converting null terminated "C" string
let source = unsafe { CStr::from_ptr(mnt.f_mntfromname.as_ptr().cast::<c_char>()) }
.to_string_lossy()
.into();
mounts.push(MountedFs { dest, fstype, source });
}
Ok(mounts)
}

75
src/fs/mounts/mod.rs Normal file
View file

@ -0,0 +1,75 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::OnceLock;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "linux")]
use linux::mounts;
#[cfg(target_os = "macos")]
use macos::mounts;
/// Details of a mounted filesystem.
#[derive(Clone)]
pub struct MountedFs {
pub dest: PathBuf,
pub fstype: String,
pub source: String,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
#[cfg(target_os = "macos")]
GetFSStatError(i32),
#[cfg(target_os = "linux")]
IOError(std::io::Error)
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Allow unreachable_patterns for windows build
#[allow(unreachable_patterns)]
match self {
#[cfg(target_os = "macos")]
Error::GetFSStatError(err) => write!(f, "getfsstat failed: {err}"),
#[cfg(target_os = "linux")]
Error::IOError(err) => write!(f, "failed to read /proc/mounts: {err}"),
_ => write!(f, "Unknown error"),
}
}
}
// A lazily initialised static map of all mounted file systems.
//
// The map contains a mapping from the mounted directory path to the
// corresponding mount information. If there's an error retrieving the mount
// list or if we're not running on Linux or Mac, the map will be empty.
//
// Initialise this at application start so we don't have to look the details
// up for every directory. Ideally this would only be done if the --mounts
// option is specified which will be significantly easier once the move
// to `clap` is complete.
pub(super) fn all_mounts() -> &'static HashMap<PathBuf, MountedFs> {
static ALL_MOUNTS: OnceLock<HashMap<PathBuf, MountedFs>> = OnceLock::new();
ALL_MOUNTS.get_or_init(|| {
// Allow unused_mut for windows build
#[allow(unused_mut)]
let mut mount_map: HashMap<PathBuf, MountedFs> = HashMap::new();
#[cfg(any(target_os = "linux", target_os = "macos"))]
if let Ok(mounts) = mounts() {
for mount in mounts {
mount_map.insert(mount.dest.clone(), mount);
}
}
mount_map
})
}

View file

@ -7,11 +7,9 @@
//! # Contributors
//! Please keep these lists sorted. If you're using vim, :sort i
use ansiterm::Style;
use phf::{phf_map, Map};
use crate::fs::File;
use crate::theme::FileColours;
#[derive(Debug, Clone)]
pub enum FileType {
@ -24,7 +22,7 @@ pub enum FileType {
Compressed,
Temp,
Compiled,
Immediate // An “immediate” file is something that can be run or activated somehow in order to
Build // A “build file is something that can be run or activated somehow in order to
// kick off the build of a project. Its usually only present in directories full of
// source code.
}
@ -32,47 +30,47 @@ pub enum FileType {
/// Mapping from full filenames to file type.
const FILENAME_TYPES: Map<&'static str, FileType> = phf_map! {
/* Immediate file - kick off the build of a project */
"Brewfile" => FileType::Immediate,
"bsconfig.json" => FileType::Immediate,
"BUILD" => FileType::Immediate,
"BUILD.bazel" => FileType::Immediate,
"build.gradle" => FileType::Immediate,
"build.sbt" => FileType::Immediate,
"build.xml" => FileType::Immediate,
"Cargo.toml" => FileType::Immediate,
"CMakeLists.txt" => FileType::Immediate,
"composer.json" => FileType::Immediate,
"configure" => FileType::Immediate,
"Containerfile" => FileType::Immediate,
"Dockerfile" => FileType::Immediate,
"Earthfile" => FileType::Immediate,
"flake.nix" => FileType::Immediate,
"Gemfile" => FileType::Immediate,
"GNUmakefile" => FileType::Immediate,
"Gruntfile.coffee" => FileType::Immediate,
"Gruntfile.js" => FileType::Immediate,
"jsconfig.json" => FileType::Immediate,
"Justfile" => FileType::Immediate,
"justfile" => FileType::Immediate,
"Makefile" => FileType::Immediate,
"makefile" => FileType::Immediate,
"meson.build" => FileType::Immediate,
"mix.exs" => FileType::Immediate,
"package.json" => FileType::Immediate,
"Pipfile" => FileType::Immediate,
"PKGBUILD" => FileType::Immediate,
"Podfile" => FileType::Immediate,
"pom.xml" => FileType::Immediate,
"Procfile" => FileType::Immediate,
"pyproject.toml" => FileType::Immediate,
"Rakefile" => FileType::Immediate,
"RoboFile.php" => FileType::Immediate,
"SConstruct" => FileType::Immediate,
"tsconfig.json" => FileType::Immediate,
"Vagrantfile" => FileType::Immediate,
"webpack.config.cjs" => FileType::Immediate,
"webpack.config.js" => FileType::Immediate,
"WORKSPACE" => FileType::Immediate,
"Brewfile" => FileType::Build,
"bsconfig.json" => FileType::Build,
"BUILD" => FileType::Build,
"BUILD.bazel" => FileType::Build,
"build.gradle" => FileType::Build,
"build.sbt" => FileType::Build,
"build.xml" => FileType::Build,
"Cargo.toml" => FileType::Build,
"CMakeLists.txt" => FileType::Build,
"composer.json" => FileType::Build,
"configure" => FileType::Build,
"Containerfile" => FileType::Build,
"Dockerfile" => FileType::Build,
"Earthfile" => FileType::Build,
"flake.nix" => FileType::Build,
"Gemfile" => FileType::Build,
"GNUmakefile" => FileType::Build,
"Gruntfile.coffee" => FileType::Build,
"Gruntfile.js" => FileType::Build,
"jsconfig.json" => FileType::Build,
"Justfile" => FileType::Build,
"justfile" => FileType::Build,
"Makefile" => FileType::Build,
"makefile" => FileType::Build,
"meson.build" => FileType::Build,
"mix.exs" => FileType::Build,
"package.json" => FileType::Build,
"Pipfile" => FileType::Build,
"PKGBUILD" => FileType::Build,
"Podfile" => FileType::Build,
"pom.xml" => FileType::Build,
"Procfile" => FileType::Build,
"pyproject.toml" => FileType::Build,
"Rakefile" => FileType::Build,
"RoboFile.php" => FileType::Build,
"SConstruct" => FileType::Build,
"tsconfig.json" => FileType::Build,
"Vagrantfile" => FileType::Build,
"webpack.config.cjs" => FileType::Build,
"webpack.config.js" => FileType::Build,
"WORKSPACE" => FileType::Build,
/* Cryptology files */
"id_dsa" => FileType::Crypto,
"id_ecdsa" => FileType::Crypto,
@ -86,7 +84,7 @@ const FILENAME_TYPES: Map<&'static str, FileType> = phf_map! {
/// extension is added also update the extension icon map.
const EXTENSION_TYPES: Map<&'static str, FileType> = phf_map! {
/* Immediate file - kick off the build of a project */
"ninja" => FileType::Immediate,
"ninja" => FileType::Build,
/* Image files */
"arw" => FileType::Image,
"avif" => FileType::Image,
@ -269,10 +267,10 @@ impl FileType {
/// Lookup the file type based on the file's name, by the file name
/// lowercase extension, or if the file could be compiled from related
/// source code.
fn get_file_type(file: &File<'_>) -> Option<FileType> {
pub(crate) fn get_file_type(file: &File<'_>) -> Option<FileType> {
// Case-insensitive readme is checked first for backwards compatibility.
if file.name.to_lowercase().starts_with("readme") {
return Some(Self::Immediate)
return Some(Self::Build)
}
if let Some(file_type) = FILENAME_TYPES.get(&file.name) {
return Some(file_type.clone())
@ -291,27 +289,3 @@ impl FileType {
None
}
}
#[derive(Debug)]
pub struct FileTypeColor;
impl FileColours for FileTypeColor {
/// Map from the file type to the display style/color for the file.
fn colour_file(&self, file: &File<'_>) -> Option<Style> {
use ansiterm::Colour::*;
match FileType::get_file_type(file) {
Some(FileType::Compiled) => Some(Yellow.normal()),
Some(FileType::Compressed) => Some(Red.normal()),
Some(FileType::Crypto) => Some(Green.bold()),
Some(FileType::Document) => Some(Green.normal()),
Some(FileType::Image) => Some(Purple.normal()),
Some(FileType::Immediate) => Some(Yellow.bold().underline()),
Some(FileType::Lossless) => Some(Cyan.bold()),
Some(FileType::Music) => Some(Cyan.normal()),
Some(FileType::Temp) => Some(White.normal()),
Some(FileType::Video) => Some(Purple.bold()),
_ => None
}
}
}

12
src/lib.rs Normal file
View file

@ -0,0 +1,12 @@
#[allow(unused)]
pub mod fs;
#[allow(unused)]
pub mod info;
#[allow(unused)]
pub mod logger;
#[allow(unused)]
pub mod options;
#[allow(unused)]
pub mod output;
#[allow(unused)]
pub mod theme;

View file

@ -22,23 +22,16 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wildcard_imports)]
use std::collections::HashMap;
use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{self, Write, ErrorKind};
use std::path::{Component, PathBuf};
use std::process::exit;
use ansiterm::{ANSIStrings, Style};
use log::*;
#[cfg(target_os = "linux")]
use proc_mounts::MountList;
#[macro_use]
extern crate lazy_static;
use crate::fs::mounts::MountedFs;
use crate::fs::{Dir, File};
use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
@ -53,42 +46,7 @@ mod options;
mod output;
mod theme;
// A lazily initialised static map of all mounted file systems.
//
// The map contains a mapping from the mounted directory path to the
// corresponding mount information. On Linux systems, this map is populated
// using the `proc-mounts` crate. If there's an error retrieving the mount
// list or if we're not running on Linux, the map will be empty.
//
// Initialise this at application start so we don't have to look the details
// up for every directory. Ideally this would only be done if the --mounts
// option is specified which will be significantly easier once the move
// to `clap` is complete.
lazy_static! {
static ref ALL_MOUNTS: HashMap<PathBuf, MountedFs> = {
#[cfg(target_os = "linux")]
match MountList::new() {
Ok(mount_list) => {
let mut m = HashMap::new();
mount_list.0.iter().for_each(|mount| {
m.insert(mount.dest.clone(), MountedFs {
dest: mount.dest.to_string_lossy().into_owned(),
fstype: mount.fstype.clone(),
source: mount.source.to_string_lossy().into(),
});
});
m
}
Err(_) => HashMap::new()
}
#[cfg(not(target_os = "linux"))]
HashMap::new()
};
}
fn main() {
use std::process::exit;
#[cfg(unix)]
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
@ -118,8 +76,11 @@ fn main() {
let theme = options.theme.to_theme(terminal_size::terminal_size().is_some());
let exa = Exa { options, writer, input_paths, theme, console_width, git };
info!("matching on exa.run");
match exa.run() {
Ok(exit_status) => {
trace!("exa.run: exit Ok(exit_status)");
exit(exit_status);
}
@ -130,6 +91,7 @@ fn main() {
Err(e) => {
eprintln!("{e}");
trace!("exa.run: exit RUNTIME_ERROR");
exit(exits::RUNTIME_ERROR);
}
}
@ -225,8 +187,13 @@ impl<'args> Exa<'args> {
Ok(f) => {
if f.points_to_directory() && ! self.options.dir_action.treat_dirs_as_files() {
trace!("matching on to_dir");
match f.to_dir() {
Ok(d) => dirs.push(d),
Err(e) if e.kind() == ErrorKind::PermissionDenied => {
warn!("Permission Denied: {e}");
exit(exits::PERMISSION_DENIED);
},
Err(e) => writeln!(io::stderr(), "{file_path:?}: {e}")?,
}
}
@ -378,4 +345,7 @@ mod exits {
/// Exit code for when the command-line options are invalid.
pub const OPTIONS_ERROR: i32 = 3;
/// Exit code for missing file permissions
pub const PERMISSION_DENIED: i32 = 13;
}

View file

@ -53,7 +53,7 @@ LONG VIEW OPTIONS
-H, --links list each file's number of hard links
-i, --inode list each file's inode number
-m, --modified use the modified timestamp field
-M, --mounts show mount details (Linux only)
-M, --mounts show mount details (Linux and MacOS only)
-n, --numeric list numeric user and group IDs
-S, --blocksize show size of allocated file system blocks
-t, --time FIELD which timestamp field to list (modified, accessed, created)

View file

@ -68,6 +68,8 @@ use std::vec::IntoIter as VecIntoIter;
use ansiterm::Style;
use scoped_threadpool::Pool;
use log::*;
use crate::fs::{Dir, File};
use crate::fs::dir_action::RecurseOptions;
use crate::fs::feature::git::GitCache;
@ -201,7 +203,7 @@ impl<'a> Render<'a> {
pub fn show_xattr_hint(&self, file: &File<'_>) -> bool {
// Do not show the hint '@' if the only extended attribute is the security
// attribute and the security attribute column is active.
let xattr_count = file.extended_attributes.len();
let xattr_count = file.extended_attributes().len();
let selinux_ctx_shown = self.opts.secattr && match file.security_context().context {
SecurityContextType::SELinux(_) => true,
SecurityContextType::None => false,
@ -250,7 +252,7 @@ impl<'a> Render<'a> {
// that they want to see them.
let xattrs: &[Attribute] = if xattr::ENABLED && self.opts.xattr {
&file.extended_attributes
file.extended_attributes()
} else {
&[]
};
@ -261,6 +263,7 @@ impl<'a> Render<'a> {
let mut dir = None;
if let Some(r) = self.recurse {
if file.is_directory() && r.tree && ! r.is_too_deep(depth.0) {
trace!("matching on to_dir");
match file.to_dir() {
Ok(d) => {
dir = Some(d);

View file

@ -350,7 +350,8 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
let mut display_hyperlink = false;
if self.options.embed_hyperlinks == EmbedHyperlinks::On {
if let Some(abs_path) = self.file.absolute_path.as_ref().and_then(|p| p.as_os_str().to_str()) {
if let Some(abs_path) = self.file.absolute_path().and_then(|p| p.as_os_str().to_str()) {
#[cfg(not(target_os = "windows"))]
bits.insert(0, ANSIString::from(format!(
"{}file://{}{}{}",
HYPERLINK_START,
@ -358,6 +359,14 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
urlencoding::encode(abs_path).replace("%2F", "/"),
HYPERLINK_END,
)));
#[cfg(target_os = "windows")]
bits.insert(0, ANSIString::from(format!(
"{}file://{}{}",
HYPERLINK_START,
abs_path.replace("\\\\?\\", ""),
HYPERLINK_END,
)));
display_hyperlink = true;
}
}

View file

@ -31,8 +31,8 @@ impl f::SubdirGitRepo {
let s = match self.status {
f::SubdirGitRepoStatus::NoRepo => style.paint("- "),
f::SubdirGitRepoStatus::GitClean => style.fg(Color::Green).paint("| "),
f::SubdirGitRepoStatus::GitDirty => style.bold().fg(Color::Red).paint("- "),
f::SubdirGitRepoStatus::GitUnknown => style.paint("- "),
f::SubdirGitRepoStatus::GitDirty => style.bold().fg(Color::Red).paint("+ "),
f::SubdirGitRepoStatus::GitUnknown => style.fg(Color::Green).bold().paint("~ "),
};
TextCell {

View file

@ -78,6 +78,19 @@ impl UiStyles {
},
},
file_type: FileType {
image: Purple.normal(),
video: Purple.bold(),
music: Cyan.normal(),
lossless: Cyan.bold(),
crypto: Green.bold(),
document: Green.normal(),
compressed: Red.normal(),
temp: White.normal(),
compiled: Yellow.normal(),
build: Yellow.bold().underline()
},
punctuation: DarkGray.bold(),
date: Blue.normal(),
inode: Purple.normal(),

View file

@ -1,6 +1,7 @@
use ansiterm::Style;
use crate::fs::File;
use crate::info::filetype::FileType;
use crate::output::file_name::Colours as FileNameColours;
use crate::output::render;
@ -58,18 +59,16 @@ pub struct Definitions {
pub struct Theme {
pub ui: UiStyles,
pub exts: Box<dyn FileColours>,
pub exts: Box<dyn FileStyle>,
}
impl Options {
#[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason
pub fn to_theme(&self, isatty: bool) -> Theme {
use crate::info::filetype::FileTypeColor;
if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && ! isatty) {
let ui = UiStyles::plain();
let exts = Box::new(NoFileColours);
let exts = Box::new(NoFileStyle);
return Theme { ui, exts };
}
@ -79,10 +78,10 @@ impl Options {
// Use between 0 and 2 file name highlighters
let exts = match (exts.is_non_empty(), use_default_filetypes) {
(false, false) => Box::new(NoFileColours) as Box<_>,
(false, true) => Box::new(FileTypeColor) as Box<_>,
( true, false) => Box::new(exts) as Box<_>,
( true, true) => Box::new((exts, FileTypeColor)) as Box<_>,
(false, false) => Box::new(NoFileStyle) as Box<_>,
(false, true) => Box::new(FileTypes) as Box<_>,
( true, false) => Box::new(exts) as Box<_>,
( true, true) => Box::new((exts, FileTypes)) as Box<_>,
};
Theme { ui, exts }
@ -144,14 +143,18 @@ impl Definitions {
}
pub trait FileColours: std::marker::Sync {
fn colour_file(&self, file: &File<'_>) -> Option<Style>;
/// Determine the style to paint the text for the filename part of the output.
pub trait FileStyle: Sync {
/// Return the style to paint the filename text for `file` from the given
/// `theme`.
fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style>;
}
#[derive(PartialEq, Debug)]
struct NoFileColours;
impl FileColours for NoFileColours {
fn colour_file(&self, _file: &File<'_>) -> Option<Style> {
struct NoFileStyle;
impl FileStyle for NoFileStyle {
fn get_style(&self, _file: &File<'_>, _theme: &Theme) -> Option<Style> {
None
}
}
@ -160,13 +163,13 @@ impl FileColours for NoFileColours {
// first one then try the second one. This lets the user provide their own
// file type associations, while falling back to the default set if not set
// explicitly.
impl<A, B> FileColours for (A, B)
where A: FileColours,
B: FileColours,
impl<A, B> FileStyle for (A, B)
where A: FileStyle,
B: FileStyle,
{
fn colour_file(&self, file: &File<'_>) -> Option<Style> {
self.0.colour_file(file)
.or_else(|| self.1.colour_file(file))
fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
self.0.get_style(file, theme)
.or_else(|| self.1.get_style(file, theme))
}
}
@ -176,17 +179,6 @@ struct ExtensionMappings {
mappings: Vec<(glob::Pattern, Style)>,
}
// Loop through backwards so that colours specified later in the list override
// colours specified earlier, like we do with options and strict mode
impl FileColours for ExtensionMappings {
fn colour_file(&self, file: &File<'_>) -> Option<Style> {
self.mappings.iter().rev()
.find(|t| t.0.matches(&file.name))
.map (|t| t.1)
}
}
impl ExtensionMappings {
fn is_non_empty(&self) -> bool {
! self.mappings.is_empty()
@ -197,8 +189,37 @@ impl ExtensionMappings {
}
}
// Loop through backwards so that colours specified later in the list override
// colours specified earlier, like we do with options and strict mode
impl FileStyle for ExtensionMappings {
fn get_style(&self, file: &File<'_>, _theme: &Theme) -> Option<Style> {
self.mappings.iter().rev()
.find(|t| t.0.matches(&file.name))
.map (|t| t.1)
}
}
#[derive(Debug)]
struct FileTypes;
impl FileStyle for FileTypes {
fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
match FileType::get_file_type(file) {
Some(FileType::Image) => Some(theme.ui.file_type.image),
Some(FileType::Video) => Some(theme.ui.file_type.video),
Some(FileType::Music) => Some(theme.ui.file_type.music),
Some(FileType::Lossless) => Some(theme.ui.file_type.lossless),
Some(FileType::Crypto) => Some(theme.ui.file_type.crypto),
Some(FileType::Document) => Some(theme.ui.file_type.document),
Some(FileType::Compressed) => Some(theme.ui.file_type.compressed),
Some(FileType::Temp) => Some(theme.ui.file_type.temp),
Some(FileType::Compiled) => Some(theme.ui.file_type.compiled),
Some(FileType::Build) => Some(theme.ui.file_type.build),
None => None
}
}
}
#[cfg(unix)]
impl render::BlocksColours for Theme {
@ -320,17 +341,17 @@ impl render::UserColours for Theme {
}
impl FileNameColours for Theme {
fn symlink_path(&self) -> Style { self.ui.symlink_path }
fn normal_arrow(&self) -> Style { self.ui.punctuation }
fn broken_symlink(&self) -> Style { self.ui.broken_symlink }
fn broken_filename(&self) -> Style { apply_overlay(self.ui.broken_symlink, self.ui.broken_path_overlay) }
fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char, self.ui.broken_path_overlay) }
fn control_char(&self) -> Style { self.ui.control_char }
fn symlink_path(&self) -> Style { self.ui.symlink_path }
fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char, self.ui.broken_path_overlay) }
fn executable_file(&self) -> Style { self.ui.filekinds.executable }
fn mount_point(&self) -> Style { self.ui.filekinds.mount_point }
fn colour_file(&self, file: &File<'_>) -> Style {
self.exts.colour_file(file).unwrap_or(self.ui.filekinds.normal)
self.exts.get_style(file, self).unwrap_or(self.ui.filekinds.normal)
}
}
@ -537,6 +558,17 @@ mod customs_test {
test!(exa_mp: ls "", exa "mp=1;34;4" => colours c -> { c.filekinds.mount_point = Blue.bold().underline(); });
test!(exa_im: ls "", exa "im=38;5;128" => colours c -> { c.file_type.image = Fixed(128).normal(); });
test!(exa_vi: ls "", exa "vi=38;5;129" => colours c -> { c.file_type.video = Fixed(129).normal(); });
test!(exa_mu: ls "", exa "mu=38;5;130" => colours c -> { c.file_type.music = Fixed(130).normal(); });
test!(exa_lo: ls "", exa "lo=38;5;131" => colours c -> { c.file_type.lossless = Fixed(131).normal(); });
test!(exa_cr: ls "", exa "cr=38;5;132" => colours c -> { c.file_type.crypto = Fixed(132).normal(); });
test!(exa_do: ls "", exa "do=38;5;133" => colours c -> { c.file_type.document = Fixed(133).normal(); });
test!(exa_co: ls "", exa "co=38;5;134" => colours c -> { c.file_type.compressed = Fixed(134).normal(); });
test!(exa_tm: ls "", exa "tm=38;5;135" => colours c -> { c.file_type.temp = Fixed(135).normal(); });
test!(exa_cm: ls "", exa "cm=38;5;136" => colours c -> { c.file_type.compiled = Fixed(136).normal(); });
test!(exa_ie: ls "", exa "bu=38;5;137" => colours c -> { c.file_type.build = Fixed(137).normal(); });
// All the while, LS_COLORS treats them as filenames:
test!(ls_uu: ls "uu=38;5;117", exa "" => exts [ ("uu", Fixed(117).normal()) ]);
test!(ls_un: ls "un=38;5;118", exa "" => exts [ ("un", Fixed(118).normal()) ]);

View file

@ -14,6 +14,7 @@ pub struct UiStyles {
pub links: Links,
pub git: Git,
pub security_context: SecurityContext,
pub file_type: FileType,
pub punctuation: Style,
pub date: Style,
@ -121,6 +122,21 @@ pub struct SecurityContext {
pub selinux: SELinuxContext,
}
/// Drawing styles based on the type of file (video, image, compressed, etc)
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FileType {
pub image: Style, // im - image file
pub video: Style, // vi - video file
pub music: Style, // mu - lossy music
pub lossless: Style, // lo - lossless music
pub crypto: Style, // cr - related to cryptography
pub document: Style, // do - document file
pub compressed: Style, // co - compressed file
pub temp: Style, // tm - temporary file
pub compiled: Style, // cm - compilation artifact
pub build: Style, // bu - file that is used to build a project
}
impl UiStyles {
pub fn plain() -> Self {
Self::default()
@ -213,6 +229,17 @@ impl UiStyles {
"mp" => self.filekinds.mount_point = pair.to_style(),
"im" => self.file_type.image = pair.to_style(),
"vi" => self.file_type.video = pair.to_style(),
"mu" => self.file_type.music = pair.to_style(),
"lo" => self.file_type.lossless = pair.to_style(),
"cr" => self.file_type.crypto = pair.to_style(),
"do" => self.file_type.document = pair.to_style(),
"co" => self.file_type.compressed = pair.to_style(),
"tm" => self.file_type.temp = pair.to_style(),
"cm" => self.file_type.compiled = pair.to_style(),
"bu" => self.file_type.build = pair.to_style(),
_ => return false,
}

View file

@ -0,0 +1 @@
"nonexistentdir": No such file or directory (os error 2)

View file

View file

@ -0,0 +1,3 @@
bin.name = "eza"
args = "nonexistentdir"
status.code = 2

View file

View file

@ -0,0 +1,21 @@
0 a
0 b
0 c
0 d
0 e
- exa
0 f
0 g
0 h
0 i
0 image.jpg.img.c.rs.log.png
19 index.svg
0 j
0 k
0 l
0 m
0 n
0 o
0 p
0 q
- vagrant

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --long --no-user --no-time --no-permissions --binary"

View file

View file

@ -0,0 +1,21 @@
0 a
0 b
0 c
0 d
0 e
- exa
0 f
0 g
0 h
0 i
0 image.jpg.img.c.rs.log.png
19 index.svg
0 j
0 k
0 l
0 m
0 n
0 o
0 p
0 q
- vagrant

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --long --no-user --no-time --no-permissions"

View file

View file

@ -0,0 +1,22 @@
Size Name
0 a
0 b
0 c
0 d
0 e
- exa
0 f
0 g
0 h
0 i
0 image.jpg.img.c.rs.log.png
19 index.svg
0 j
0 k
0 l
0 m
0 n
0 o
0 p
0 q
- vagrant

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --long --no-user --no-time --header --no-permissions"

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --long --no-user --no-time --no-filesize --no-permissions --recurse --links"

View file

@ -0,0 +1,21 @@
a
b
c
d
e
exa
f
g
h
i
image.jpg.img.c.rs.log.png
index.svg
j
k
l
m
n
o
p
q
vagrant

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --long --no-user --no-permissions --no-time --no-filesize --recurse --level 2"

View file

@ -1,21 +1,21 @@
.rw-r--r-- a
.rw-r--r-- b
.rw-r--r-- c
.rw-r--r-- d
.rw-r--r-- e
drwxr-xr-x exa
.rw-r--r-- f
.rw-r--r-- g
.rw-r--r-- h
.rw-r--r-- i
.rw-r--r-- image.jpg.img.c.rs.log.png
.rw-r--r-- index.svg
.rw-r--r-- j
.rw-r--r-- k
.rw-r--r-- l
.rw-r--r-- m
.rw-r--r-- n
.rw-r--r-- o
.rw-r--r-- p
.rw-r--r-- q
drwxr-xr-x vagrant
a
b
c
d
e
exa
f
g
h
i
image.jpg.img.c.rs.log.png
index.svg
j
k
l
m
n
o
p
q
vagrant

View file

@ -1,2 +1,2 @@
bin.name = "eza"
args = "tests/itest --long --no-user --no-time --no-filesize"
args = "tests/itest --long --no-user --no-time --no-filesize --no-permissions"

View file

@ -0,0 +1,21 @@
exa
vagrant
tests/itest/exa:
sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
tests/itest/exa/sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss:
tests/itest/vagrant:
debug
dev
log
tests/itest/vagrant/debug:
tests/itest/vagrant/dev:
tests/itest/vagrant/log:
run
tests/itest/vagrant/log/run:

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --recurse --long --no-user --no-time --no-filesize --no-permissions --only-dirs"

View file

View file

@ -0,0 +1,21 @@
exa
vagrant
tests/itest/exa:
sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
tests/itest/exa/sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss:
tests/itest/vagrant:
debug
dev
log
tests/itest/vagrant/debug:
tests/itest/vagrant/dev:
tests/itest/vagrant/log:
run
tests/itest/vagrant/log/run:

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --only-dirs --recurse"

View file

View file

@ -0,0 +1,2 @@
exa
vagrant

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --only-dirs"

View file

View file

@ -0,0 +1,47 @@
a
b
c
d
e
exa
f
g
h
i
image.jpg.img.c.rs.log.png
index.svg
j
k
l
m
n
o
p
q
vagrant
tests/itest/exa:
file.c -> djihisudjuhfius
sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
tests/itest/exa/sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss:
Makefile
tests/itest/vagrant:
debug
dev
log
tests/itest/vagrant/debug:
symlinking -> a
tests/itest/vagrant/dev:
main.bf
tests/itest/vagrant/log:
file.png
run
tests/itest/vagrant/log/run:
run.log.text
sps.log.text

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --recurse --long --no-user --no-time --no-filesize --no-permissions"

View file

View file

@ -0,0 +1,47 @@
a
b
c
d
e
exa
f
g
h
i
image.jpg.img.c.rs.log.png
index.svg
j
k
l
m
n
o
p
q
vagrant
tests/itest/exa:
file.c -> djihisudjuhfius
sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
tests/itest/exa/sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss:
Makefile
tests/itest/vagrant:
debug
dev
log
tests/itest/vagrant/debug:
symlinking -> a
tests/itest/vagrant/dev:
main.bf
tests/itest/vagrant/log:
file.png
run
tests/itest/vagrant/log/run:
run.log.text
sps.log.text

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --recurse"

View file

View file

@ -0,0 +1,34 @@
tests/itest
├── a
├── b
├── c
├── d
├── e
├── exa
│ ├── file.c -> djihisudjuhfius
│ └── sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
│ └── Makefile
├── f
├── g
├── h
├── i
├── image.jpg.img.c.rs.log.png
├── index.svg
├── j
├── k
├── l
├── m
├── n
├── o
├── p
├── q
└── vagrant
├── debug
│ └── symlinking -> a
├── dev
│ └── main.bf
└── log
├── file.png
└── run
├── run.log.text
└── sps.log.text

View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --tree --long --no-user --no-time --no-filesize --no-permissions"

View file

View file

@ -0,0 +1,34 @@
tests/itest
├── a
├── b
├── c
├── d
├── e
├── exa
│ ├── file.c -> djihisudjuhfius
│ └── sssssssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
│ └── Makefile
├── f
├── g
├── h
├── i
├── image.jpg.img.c.rs.log.png
├── index.svg
├── j
├── k
├── l
├── m
├── n
├── o
├── p
├── q
└── vagrant
├── debug
│ └── symlinking -> a
├── dev
│ └── main.bf
└── log
├── file.png
└── run
├── run.log.text
└── sps.log.text

2
tests/cmd/tree_unix.toml Normal file
View file

@ -0,0 +1,2 @@
bin.name = "eza"
args = "tests/itest --tree"

View file

@ -5,4 +5,7 @@
rustfmt.enable = true;
shellcheck.enable = true;
};
settings = {
formatter.shellcheck.includes = ["*.sh" "./completions/bash/eza"];
};
}