diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 83df45ad..efea1a38 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,16 @@ # SPDX-FileCopyrightText: 2026 fxrdhan # SPDX-License-Identifier: EUPL-1.2 # -# Publishes a release when a v* tag is pushed. +# Publishes a release when a v* tag is pushed: the GitHub release with its +# binaries, then the crate on crates.io and the Homebrew formula. # # The binaries are built on GitHub runners, one target per runner, natively. -# The justfile's `cross` recipe builds seven targets through Docker instead; -# that path needs a working `--no-default-features` build, which the tree does -# not currently have, and it runs containers on the maintainer's machine. This -# does neither. +# The justfile's `cross` recipe builds seven targets through Docker instead, +# in containers on the maintainer's machine. This does not. # -# The release is published as soon as every binary is built. +# The release is published as soon as every binary is built. Its notes are +# the version's section of CHANGELOG.md, the one written by hand: the commit +# titles git-cliff would list say little once a pull request is squashed. # # Run any other way, by hand or by a pull request that changes this file, it # is a dry run: everything is built and packaged, under a stand-in version, @@ -100,6 +101,37 @@ jobs: name: binary-${{ matrix.target }} path: dist/* if-no-files-found: error + notes: + name: Release notes + runs-on: ubuntu-latest + outputs: + notes: ${{ steps.notes.outputs.notes }} + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Take the version's section of CHANGELOG.md + id: notes + run: | + # The first `version =` in Cargo.toml is the package's. + version=$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1) + if [ "${GITHUB_EVENT_NAME}" = push ] && [ "${VERSION}" != "v${version}" ]; then + echo "::error file=Cargo.toml::The tag is ${VERSION}, but Cargo.toml says ${version}." + exit 1 + fi + awk -v head="## [${version}]" ' + index($0, "## [") == 1 { if (on) exit; on = index($0, head) == 1; next } + on + ' CHANGELOG.md > NOTES.md + if ! grep -q '[^[:space:]]' NOTES.md; then + echo "::error file=CHANGELOG.md::CHANGELOG.md has no section for ${version}." + exit 1 + fi + cat NOTES.md + { + echo "notes<> "$GITHUB_OUTPUT" docs: name: Man pages and completions runs-on: ubuntu-latest @@ -127,7 +159,7 @@ jobs: if-no-files-found: error publish: name: Publish the release - needs: [binaries, docs] + needs: [binaries, docs, notes] # Only a pushed tag publishes; run by hand, even on a tag, it is a dry run. if: github.event_name == 'push' && github.ref_type == 'tag' runs-on: ubuntu-latest @@ -136,8 +168,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v7 - with: - fetch-depth: 0 - name: Collect the artifacts uses: actions/download-artifact@v4 with: @@ -148,10 +178,10 @@ jobs: find artifacts -type f -exec mv {} dist/ \; ls -l dist - name: Write the release notes + env: + NOTES: ${{ needs.notes.outputs.notes }} run: | - version="${GITHUB_REF_NAME}" - cargo install git-cliff --locked --version 2.13.1 - git cliff -c .config/cliff.toml -t "${version}" --current > NOTES.md + printf '%s\n' "${NOTES}" > NOTES.md { echo echo "## Checksums" @@ -168,9 +198,17 @@ jobs: --title "lez ${GITHUB_REF_NAME}" \ --notes-file NOTES.md \ dist/* + homebrew: + name: Update the Homebrew formula + needs: publish + # Its own job, so a tap token that cannot write fails here alone rather + # than marking the release, or anything waiting on it, failed. + if: "!contains(github.ref_name, '-')" + runs-on: ubuntu-latest + steps: - name: Update Homebrew formula uses: mislav/bump-homebrew-formula-action@f865c8b0dbebd6e263cc06782a0e974c61cf12d2 # v4.2 - if: "!contains(github.ref_name, '-') && env.COMMITTER_TOKEN != ''" + if: env.COMMITTER_TOKEN != '' with: formula-name: lez homebrew-tap: fxrdhan/homebrew-tap @@ -179,3 +217,35 @@ jobs: commit-message: "bump lez to ${{ github.ref_name }}" env: COMMITTER_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + crates-io: + name: Publish to crates.io + # After the GitHub release, because `cargo binstall` takes the version + # from crates.io and the binaries from that release. A dry run, which + # releases nothing, packages and builds the crate without uploading it. + needs: publish + if: ${{ !cancelled() && (needs.publish.result == 'success' || github.event_name != 'push') }} + runs-on: ubuntu-latest + permissions: + contents: read + # Trusted Publishing: crates.io trusts this workflow, so no token is + # stored. Configured under the crate's Settings > Trusted Publishing. + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 # master + with: + toolchain: stable + - name: Check the package + if: github.event_name != 'push' + run: cargo publish --dry-run --locked + - name: Authenticate with crates.io + id: auth + if: github.event_name == 'push' + uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 + - name: Publish + if: github.event_name == 'push' + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + run: cargo publish --locked diff --git a/CHANGELOG.md b/CHANGELOG.md index 1adba12c..24ad9539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,54 @@ SPDX-License-Identifier: EUPL-1.2 --> # Changelog +## [0.28.5] - 2026-10-04 + +### Features & Enhancements + +- **NUL-Separated Stdin**: Add `--stdin0`, which reads paths separated by NUL as written by `find -print0` or `fd -0`, whatever `LEZ_STDIN_SEPARATOR` says. The last of `--stdin` and `--stdin0` wins ([#143](https://github.com/fxrdhan/lez/pull/143)). +- **Styled Link Targets**: Combine `ln=target` with style attributes, both in `LS_COLORS`/`LEZ_COLORS` (`ln=target;3`) and in `theme.yml` (`foreground: target` with `is_italic: true`). Broken links (`or=`) are unaffected ([#135](https://github.com/fxrdhan/lez/issues/135), [#136](https://github.com/fxrdhan/lez/pull/136)). +- **Odin Icon**: Give Odin source files an icon ([#149](https://github.com/fxrdhan/lez/pull/149)). + +### Behaviour Changes + +- **Shell-Safe Quoting**: `--quotes=auto` quotes every name a shell would read differently, as GNU `ls` does, and writes a name holding a control character in ANSI-C quotes (`$'new\nline'`), which a shell reads back as the same name. `--quotes=never` and Windows keep the old escapes ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **JSON Output**: `--json` writes every separator without a space, leaves the table's padding out of dates, ends the document with a newline, and leaves a link's target out under `--no-symlink-targets` ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Environment Values Are Checked**: `TIME_STYLE` is read as GNU `ls` reads it, `locale` and `posix-STYLE` included. An invalid `TIME_STYLE` or `LEZ_MIN_LUMINANCE` is now an error (exit 3) instead of a silent fallback ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Strict Mode**: Accept the flags a view prints without `--long` (`--total-size`, `--print-total`, and the tree's `--extended` and `--mounts`), and refuse a layout flag beside `--code`, which replaces every layout ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Security Context on Every Platform**: `-Z` shows a `?` column on macOS and Windows instead of being dropped silently ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Locale-Aware Numbers**: macOS and Windows group digits the way the locale does, so macOS under `LANG=C` prints `15003` rather than `15,003` ([#149](https://github.com/fxrdhan/lez/pull/149)). + +### Bug Fixes & Hardening + +- **Multi-Hop Symlink Chains**: Resolve each hop from its own link's directory, so `a -> b -> file.pdf` is followed to the end. A revisited link or a chain longer than 32 links counts as broken, and `ln=target` colours a link by the end of its chain ([#138](https://github.com/fxrdhan/lez/issues/138), [#147](https://github.com/fxrdhan/lez/pull/147)). +- **Classify Indicators on Targets**: A link to `/` no longer prints `-> //`, a target keeps the trailing slash it was written with, and `-XF` classifies a link by the end of its chain (`dir/`, `run*`) ([#140](https://github.com/fxrdhan/lez/issues/140), [#147](https://github.com/fxrdhan/lez/pull/147)). +- **Absolute Paths of Links**: `--absolute=follow` keeps a link's own name in the long view instead of printing its target on both sides of the arrow, and gives a broken link in the working directory an absolute path, as `--hyperlink` now does too ([#141](https://github.com/fxrdhan/lez/issues/141), [#142](https://github.com/fxrdhan/lez/issues/142), [#145](https://github.com/fxrdhan/lez/pull/145), [#147](https://github.com/fxrdhan/lez/pull/147)). +- **Link Target Colours**: Paint the path after `->` in its file-kind colour, so directories, executables, pipes, sockets and devices keep theirs ([#137](https://github.com/fxrdhan/lez/issues/137), [#144](https://github.com/fxrdhan/lez/pull/144)). +- **Dereference (`-X`)**: Every column that describes the target (type, permissions, size, blocks, owner, links, inode and timestamps) reads the same file, a broken link falls back to its own permissions, `--only-dirs -X` keeps links to directories, and `--only-files -X` keeps links that end at a regular file ([#139](https://github.com/fxrdhan/lez/issues/139), [#146](https://github.com/fxrdhan/lez/pull/146)). +- **Missing Targets (`mi=`)**: `mi=` in `LS_COLORS` now colours the missing path a broken link points to, falling back to `or` when unset. An empty, `0` or `00` value of `mi`, `ca` or `mh`, as `dircolors` writes them, reads as unset ([#141](https://github.com/fxrdhan/lez/issues/141), [#145](https://github.com/fxrdhan/lez/pull/145), [#149](https://github.com/fxrdhan/lez/pull/149)). +- **Themes and Icons**: The `l` in the permissions column keeps only the link's attributes under `ln=target`. Built-in icons are kept when a theme sets default ones, totals stay bold when a theme leaves `colourful` out, an icon is painted in its name's colour, and the user's folders get their icons from a relative path ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Tree, Grid and Colour Scale**: `lez -lT --color-scale` without a path shades the whole tree, `-T -f` draws the edges of the whole tree, and the grid-details view spaces its columns and headers like the long view ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Archives**: Entry sizes come from the archive, PAX records included, and a listing cut short ends with `… (more than 500 entries)` instead of a made-up entry ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Lines of Code**: A followed link's language is named after its target, nested block comments stay whole, Perl POD that opens with any command is counted, and here-documents in shell, Ruby, Perl and PHP count as text ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Options and Configuration**: Error messages name the variable a bad value came from, `--total-size` costs no recursive I/O in a view that never prints sizes, a discovered config file that does not parse is reported, the `absolute` key takes effect, and an empty `LEZ_CONFIG_FILE` is passed over ([#149](https://github.com/fxrdhan/lez/pull/149)). +- **Output**: The listing is flushed before a message on stderr, `--warn-hidden` says "1 hidden item", a plist keeps its newlines, and a directory no longer holds its descriptor open after it is read ([#149](https://github.com/fxrdhan/lez/pull/149)). + +### Documentation + +- **Docs Checked Against the Binary**: The README and the man pages stop documenting what lez does not read (a `recent` time style, `CLICOLOR`, `LEZ_OVERRIDE_AUTO_COLOR`, the `ic` colour key) and start documenting what it does (`relative-recent:DAYS`, `LEZ_DEBUG`, exit status 2, the `hw` and Finder-tag colour keys, every accepted ANSI code). `--spacing` gets its real range, the theme outline its missing keys, the theme schema accepts styled symlinks, and `-O`'s help names Linux ([#152](https://github.com/fxrdhan/lez/pull/152)). + +### Testing & Quality Assurance + +- **Test Audit Against Independent Oracles**: Every test that compared a fragment of the output now compares the whole of it against an independent source (`read_dir` order, `stat`, `GetFileAttributesW`, `locale -k`, and GNU `ls` where it is the reference), and each fix fails its test against the old code. Line coverage rises from 91.98% to 93.35% ([#149](https://github.com/fxrdhan/lez/pull/149)). + +### Distribution & CI Automation + +- **CI Gates**: A single `CI result` check stands for the whole run, the stable rows build with stable Rust, Clippy runs on macOS and Windows, every cargo command uses `--locked`, the fuzz targets are compiled in the lint job, RustSec advisories are scanned weekly, and actions are pinned to commits with read-only tokens ([#150](https://github.com/fxrdhan/lez/pull/150), [#151](https://github.com/fxrdhan/lez/pull/151)). +- **Release Dry Run**: Run by hand or by a pull request that changes it, the release workflow builds and packages every target without publishing ([#151](https://github.com/fxrdhan/lez/pull/151)). +- **crates.io Publishing**: The release workflow publishes the crate to crates.io once the GitHub release is out, by Trusted Publishing, so no registry token is stored; a dry run checks the package ([#152](https://github.com/fxrdhan/lez/pull/152)). +- **Release Notes from the Changelog**: The GitHub release notes are this file's section for the version, and a tag that does not match `Cargo.toml` stops the release before anything is published. The Homebrew bump runs in a job of its own, so a tap token that cannot write no longer marks the release failed ([#152](https://github.com/fxrdhan/lez/pull/152)). +- **Dependency Bumps**: The weekly bump now pushes its branch and opens its pull request with a CI run, the Nix canary substitutes from cache.nixos.org, and the flake inputs are current ([#148](https://github.com/fxrdhan/lez/pull/148)). + ## [0.28.4] - 2026-09-14 ### Features & Enhancements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1787487d..41a532b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,8 @@ If you want more information on the tests please read [TESTING.md](TESTING.md). ## Creating a PR -First, use the pull request template. +Branch off `dev` and open the pull request against `dev`; `main` only +receives releases. Use the pull request template. Please make sure that the thing you worked on... actually works. Make sure to also add how you ensured this in the PR description. Further, it's expected @@ -98,6 +99,10 @@ you. Most clippy issues can be resolved with `cargo clippy --fix` (although it might be educational to fix them yourself). If you have reuse issues, you can run the following command to annotate your code: +```sh +reuse annotate --copyright "Your Name" --license EUPL-1.2 path/to/file +``` + Here are the absolute basics: - your commit summary MUST follow conventional commits. - your commits SHOULD be separated into small, logical chunks. diff --git a/Cargo.lock b/Cargo.lock index ab9e8de8..956065f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "lez" -version = "0.28.4" +version = "0.28.5" dependencies = [ "ansi-width", "backtrace", diff --git a/Cargo.toml b/Cargo.toml index 821865ac..60cba15a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ readme = "README.md" homepage = "https://github.com/fxrdhan/lez" license = "EUPL-1.2" repository = "https://github.com/fxrdhan/lez" -version = "0.28.4" +version = "0.28.5" [package.metadata.deb] diff --git a/README.md b/README.md index 8a27e1da..4c7ec0f6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ SPDX-License-Identifier: EUPL-1.2 **`lez`** is a fast, modern file-listing command-line tool with smart defaults, enhanced file icons, Git integration, and continuous performance improvements. - **Fast & Lightweight:** Written in modern Rust (2024 Edition) with multithreaded directory scanning via Rayon. -- **Rich Visuals:** Syntax highlighting, colored CLI help output, Nerd Font icons, and automatic luminance color scaling. +- **Rich Visuals:** Colors by file type, colored CLI help output, Nerd Font icons, and automatic luminance color scaling. - **Git Integration:** View file and repo status (`M`odified, `U`ntracked, `I`gnored, etc.) directly in the file listing. - **Built-in Tree View:** Hierarchical directory tree out of the box (`lez --tree`). - **Structured Data Export:** Full metadata serialization via `--json` in complete parity with the long view. @@ -85,7 +85,7 @@ hyperfine --warmup 3 'lez --tree ~/.cargo/registry' 'eza --tree ~/.cargo/registr | **Nerd Font Icons & Color Themes** | ✅ | ✅ | ✅ | | **Directory Tree View** (`--tree`) | ✅ | ✅ | ✅ | | **Hyperlink Support** (`--hyperlink` OSC 8) | ✅ | ✅ | ✅ | -| **Custom Column Order** (`--blocks`) | ❌ | ❌ | ✅ | +| **Custom Column Order** (`lsd --blocks`) | ❌ | ❌ | ✅ | | **Classic GNU `ls` Mode** (`--classic`) | ❌ | ❌ | ✅ | | **Unicode Emoji Fallback** (`--icon-theme unicode`) | ❌ | ❌ | ✅ | | **Multithreaded Traversal** (Rayon Engine) | ✅ | ⚠️ Limited | ❌ | @@ -108,6 +108,8 @@ hyperfine --warmup 3 'lez --tree ~/.cargo/registry' 'eza --tree ~/.cargo/registr

Installation

+`lez` is available for macOS, Linux, and Windows. [INSTALL.md](INSTALL.md) covers every channel, plus shell completions and the man pages. + ### Homebrew (macOS & Linux) Install from the official [fxrdhan tap](https://github.com/fxrdhan/homebrew-tap): @@ -185,12 +187,6 @@ nix run github:fxrdhan/lez --- -# Installation - -`lez` is available for macOS, Linux, and Windows. Detailed platform-specific installation instructions can be found in [INSTALL.md](INSTALL.md). - ---- -

Command-line options

@@ -216,7 +212,7 @@ nix run github:fxrdhan/lez - **--colo[u]r-scale=(fields)**: highlight levels of `fields` distinctly (all, age, size) - **--color-scale-mode=(mode)**: use gradient or fixed colors in `--color-scale` (`fixed` or `gradient`) - **--icons[=(when)]**: when to display icons (always, auto, never; requires '=' if value provided) -- **--spacing=(spaces)**: number of spaces between columns in grid views (default: 2, range: 0..=255) +- **--spacing=(spaces)**: number of spaces between columns (default: 2 in the grid views, 1 in the long view; at most 1000) - **--no-symlink-targets**: do not show symlink targets (the `-> ...`) - **--quotes=(when)**: when to quote file names (always, auto, never; requires '=' if value provided) - **--summary**: display total summary statistics of entries (directories, files, symlinks, and total) @@ -314,7 +310,7 @@ Some of the options accept parameters: - Valid **--colo\[u\]r** options are **always**, **automatic** (or **auto** for short), and **never**. - Valid sort fields are **accessed**, **changed**, **created**, **extension**, **Extension**, **inode**, **lexicographic**, **Lexicographic**, **modified**, **name**, **Name**, **path**, **Path**, **size**, **block**, **type**, and **none**. Fields starting with a capital letter sort uppercase before lowercase. The modified field has the aliases **date**, **time**, **mod**, **old**, and **oldest**, while its reverse has the aliases **age**, **new**, and **newest**. The **block** field has the aliases **blocks** and **blocksize**. The **lexicographic** field has the aliases **lex** and **lg**, and compares names code point by code point — no natural ordering of digit runs, no locale collation — so **Lexicographic** matches `ls` under the C locale. - Valid time fields are **modified**, **changed**, **accessed**, and **created**. -- Valid time styles are **default**, **iso**, **long-iso**, **full-iso**, **relative**, and **relative-recent** (or **recent**). +- Valid time styles are **default**, **iso**, **long-iso**, **full-iso**, **relative**, **relative-recent** (relative times for the last 7 days, the default style before that; **relative-recent:DAYS** sets the window), and a custom **+FORMAT**. See the `man` pages for further documentation of usage. They are available: - online [in the repo](https://github.com/fxrdhan/lez/tree/main/man) @@ -390,9 +386,8 @@ An annotated sample configuration is provided in [`docs/config.example.toml`](do | `LEZ_NO_EMPTY_DIR_ICON` / `EZA_NO_EMPTY_DIR_ICON` | Set to anything to give every directory the same icon. Distinguishing an empty one costs a filesystem round trip per directory, which is slow on FUSE and network mounts. | | `LEZ_STDIN_SEPARATOR` / `EZA_STDIN_SEPARATOR` | Delimiter for paths read from standard input with `--stdin`; ignored by `--stdin0` (default: newline `\n`). Supports escape sequences (e.g. `\0`, `\n`, `\t`, `\x00`) and `null`/`nul`. | | `LEZ_SIZE_DIGITS` / `EZA_SIZE_DIGITS` | Default number of digits (1..=8) to display for formatted file sizes, the decimal point counting as one (default: `3`). | -| `LEZ_OVERRIDE_AUTO_COLOR` | Force automatic color detection behavior. | | `TIME_STYLE` | Default timestamp format style (`default`, `iso`, `long-iso`, `full-iso`, `relative`, `relative-recent`, or `+`), also read in GNU `ls`'s `locale` and `posix-