mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-02 05:31:13 -03:00
Compare commits
212 Commits
3cb939c9a8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11d6e92cb5 | ||
|
|
eb535322d0 | ||
|
|
cb2f279550 | ||
|
|
dc9668c8a4 | ||
|
|
a3b009dd43 | ||
|
|
ccfbfe9965 | ||
|
|
cb983e5fb7 | ||
|
|
f48166ff36 | ||
|
|
25cc01845a | ||
|
|
bb74d8155e | ||
|
|
240647327a | ||
|
|
04c7ba5ca0 | ||
|
|
7b358d2122 | ||
|
|
ff0afd14c2 | ||
|
|
17ef326c8f | ||
|
|
9a56334292 | ||
|
|
821efeb99c | ||
|
|
960b96750a | ||
|
|
79bf53aaa5 | ||
|
|
8dee291200 | ||
|
|
8513bb8dc4 | ||
|
|
17fdd2f153 | ||
|
|
cbc3cd21f7 | ||
|
|
7ddc8ee195 | ||
|
|
0a2879440a | ||
|
|
07b552aa78 | ||
|
|
8067a5c2c8 | ||
|
|
a2bfd93451 | ||
|
|
068fa42202 | ||
|
|
ffaa07d687 | ||
|
|
4ad3c48e3f | ||
|
|
19e0c8b6b9 | ||
|
|
0f81bb0748 | ||
|
|
cd75d3d3ca | ||
|
|
8250507a0f | ||
|
|
2c6b76d5fe | ||
|
|
78633cab0f | ||
|
|
954c68c0b1 | ||
|
|
01b848bbbb | ||
|
|
b6f8a7bf4a | ||
|
|
bf59785377 | ||
|
|
4c30c9ec2d | ||
|
|
684d360ece | ||
|
|
30b9cababa | ||
|
|
29182dae54 | ||
|
|
f598a7b444 | ||
|
|
5d317be52c | ||
|
|
e59a61e5e6 | ||
|
|
1284527fac | ||
|
|
ec52a999b3 | ||
|
|
afd869be2a | ||
|
|
d72e323a6f | ||
|
|
d383733747 | ||
|
|
1f18b9715f | ||
|
|
5876ff66ff | ||
|
|
ee4eea51ec | ||
|
|
a3984ace4a | ||
|
|
4bdd35b8d1 | ||
|
|
a93fcd97a7 | ||
|
|
f86c9af455 | ||
|
|
f69f074f66 | ||
|
|
f0054336ea | ||
|
|
959cbb4259 | ||
|
|
fa6cbbdb40 | ||
|
|
2b378b9c1f | ||
|
|
2863960836 | ||
|
|
c003ec3795 | ||
|
|
51ab1f5d35 | ||
|
|
01b9fd9e31 | ||
|
|
b2c23eb397 | ||
|
|
ef8e62727c | ||
|
|
638777a4de | ||
|
|
6705d27f93 | ||
|
|
ef626cfdf9 | ||
|
|
30976d8970 | ||
|
|
d601ceb55b | ||
|
|
6e036740de | ||
|
|
e25ebf1067 | ||
|
|
1f870b360a | ||
|
|
c1a6f6ddc8 | ||
|
|
184f4f6571 | ||
|
|
3dc36f74bc | ||
|
|
9374410bb6 | ||
|
|
1e0ff8712d | ||
|
|
e697f960c8 | ||
|
|
cab3bdabc4 | ||
|
|
5c6acdee09 | ||
|
|
a9cc505d62 | ||
|
|
6c6b53cdd8 | ||
|
|
f5ff9aac2b | ||
|
|
0ddad4fcb1 | ||
|
|
7d1604a116 | ||
|
|
aa5ecd0efa | ||
|
|
0fafff2c89 | ||
|
|
1b18d08611 | ||
|
|
5a2e9f4f3c | ||
|
|
c592b5d957 | ||
|
|
77285d46b8 | ||
|
|
efb0223da1 | ||
|
|
b6f30f11e4 | ||
|
|
4296e9bd75 | ||
|
|
894ca81464 | ||
|
|
7c53bded3a | ||
|
|
163f25d516 | ||
|
|
d8e73d2263 | ||
|
|
fb29c85a62 | ||
|
|
e071de3b68 | ||
|
|
ed6fe3f315 | ||
|
|
4f539dffaf | ||
|
|
d885e0efd7 | ||
|
|
330e897acc | ||
|
|
b638aa198f | ||
|
|
fd44c23678 | ||
|
|
f84179f8fe | ||
|
|
71d6ec4ab9 | ||
|
|
683e4c8d15 | ||
|
|
d7cc3c7bb6 | ||
|
|
9370830733 | ||
|
|
161f31f42b | ||
|
|
5998421410 | ||
|
|
5b1e163f22 | ||
|
|
7c5fc85d96 | ||
|
|
2f9f46b2a5 | ||
|
|
4b069b51e7 | ||
|
|
398fc17b81 | ||
|
|
12fa0d8b3d | ||
|
|
0441bdc634 | ||
|
|
d0e47cf58a | ||
|
|
d35aa3860a | ||
|
|
5971e79c3f | ||
|
|
dad660cda5 | ||
|
|
8328e53050 | ||
|
|
4aadeea184 | ||
|
|
df5067cc1c | ||
|
|
3d708d6fc1 | ||
|
|
a564238d82 | ||
|
|
64443aa173 | ||
|
|
d2c2b23d1f | ||
|
|
4e3898d0d7 | ||
|
|
3ad45d8fb1 | ||
|
|
39bd54cb49 | ||
|
|
281399561b | ||
|
|
e5f57b1daf | ||
|
|
6c04a72697 | ||
|
|
1034945690 | ||
|
|
e2b18fc5b6 | ||
|
|
319b093ef8 | ||
|
|
ab2678082e | ||
|
|
81e8eebd8d | ||
|
|
2b41f132be | ||
|
|
688d1954a8 | ||
|
|
96695a2859 | ||
|
|
f2b0706494 | ||
|
|
c91bfba08c | ||
|
|
cc40fa4a4c | ||
|
|
ff6ee65deb | ||
|
|
1771a325aa | ||
|
|
58648054c0 | ||
|
|
27fb4d6731 | ||
|
|
6701b7f6c8 | ||
|
|
e175a317af | ||
|
|
7b98a275fe | ||
|
|
b78dc4fbec | ||
|
|
12e97ea7fc | ||
|
|
af8594c611 | ||
|
|
006fa86ef4 | ||
|
|
9b04300dc3 | ||
|
|
c80496fad1 | ||
|
|
ca56949028 | ||
|
|
fa74d0fe54 | ||
|
|
59f3719e95 | ||
|
|
170c171e85 | ||
|
|
c33ca660e3 | ||
|
|
f7c336021b | ||
|
|
523e25df17 | ||
|
|
c8b28d4d24 | ||
|
|
ba35214e1e | ||
|
|
d05d8557a7 | ||
|
|
a3dc57873c | ||
|
|
0c078c179d | ||
|
|
ca443e2e54 | ||
|
|
63c3306e6c | ||
|
|
923d0b7974 | ||
|
|
52998635f9 | ||
|
|
1ccf4ad480 | ||
|
|
23b5b01242 | ||
|
|
ca2b5dc40b | ||
|
|
0dfe06f4c9 | ||
|
|
4e47f47d85 | ||
|
|
f3e43e932f | ||
|
|
1dfc75bb9c | ||
|
|
fa33f6f0e0 | ||
|
|
31363120aa | ||
|
|
2304077e0d | ||
|
|
86c052b6ba | ||
|
|
68472da48a | ||
|
|
4b172fc735 | ||
|
|
944ab91fab | ||
|
|
34535fcb61 | ||
|
|
9e4eb37696 | ||
|
|
dda76d7f18 | ||
|
|
fdb1d95521 | ||
|
|
937f3bc6cb | ||
|
|
ebc32adc09 | ||
|
|
a4b6348315 | ||
|
|
b21a4a7197 | ||
|
|
0cd227533f | ||
|
|
5eb7687a64 | ||
|
|
8d6426295e | ||
|
|
85e76ba356 | ||
|
|
fee4288122 | ||
|
|
413246a93d |
49
.cirrus.yml
49
.cirrus.yml
@@ -1,49 +0,0 @@
|
||||
env:
|
||||
CIRRUS_CLONE_DEPTH: 100
|
||||
CI: 1
|
||||
|
||||
linux_task:
|
||||
matrix:
|
||||
- name: alpine
|
||||
container: &step
|
||||
image: ghcr.io/krobelus/fish-ci/alpine:latest
|
||||
memory: 4GB
|
||||
- name: ubuntu-oldest-supported
|
||||
container:
|
||||
<<: *step
|
||||
image: ghcr.io/krobelus/fish-ci/ubuntu-oldest-supported:latest
|
||||
tests_script:
|
||||
# cirrus at times gives us 32 procs and 2 GB of RAM
|
||||
# Unrestriced parallelism results in OOM
|
||||
- lscpu || true
|
||||
- (cat /proc/meminfo | grep MemTotal) || true
|
||||
- mkdir build && cd build
|
||||
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
|
||||
- ninja -j 6 fish
|
||||
- ninja fish_run_tests
|
||||
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
|
||||
|
||||
freebsd_task:
|
||||
matrix:
|
||||
- name: FreeBSD Stable
|
||||
freebsd_instance:
|
||||
image: freebsd-15-0-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
|
||||
tests_script:
|
||||
- pkg update
|
||||
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
|
||||
# libclang.so is a required build dependency for rust-c++ ffi bridge
|
||||
- pkg install -y llvm
|
||||
# BSDs have the following behavior: root may open or access files even if
|
||||
# the mode bits would otherwise disallow it. For example root may open()
|
||||
# a file with write privileges even if the file has mode 400. This breaks
|
||||
# our tests for e.g. cd and path. So create a new unprivileged user to run tests.
|
||||
- pw user add -n fish-user -s /bin/csh -d /home/fish-user
|
||||
- mkdir -p /home/fish-user
|
||||
- chown -R fish-user /home/fish-user
|
||||
- mkdir build && cd build
|
||||
- chown -R fish-user ..
|
||||
- sudo -u fish-user -s whoami
|
||||
- sudo -u fish-user -s FISH_TEST_MAX_CONCURRENCY=1 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
|
||||
- sudo -u fish-user -s ninja -j 6 fish
|
||||
- sudo -u fish-user -s ninja fish_run_tests
|
||||
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
|
||||
@@ -30,5 +30,5 @@ max_line_length = unset
|
||||
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
|
||||
max_line_length = 72
|
||||
|
||||
[*.yml]
|
||||
[*.{toml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
10
.github/actions/install-sphinx/action.yml
vendored
10
.github/actions/install-sphinx/action.yml
vendored
@@ -13,11 +13,9 @@ runs:
|
||||
command -v uv
|
||||
command -v uvx
|
||||
# Check that pyproject.toml and the lock file are in sync.
|
||||
# TODO Use "uv" to install Python as well.
|
||||
: 'Note that --no-managed-python below would be implied but be explicit'
|
||||
uv='env UV_PYTHON=python uv --no-managed-python'
|
||||
$uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
|
||||
# Install globally.
|
||||
sudo $uv pip install --group=dev --system --break-system-packages
|
||||
uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
|
||||
uv venv ~/.local --allow-existing
|
||||
uv pip install --group=dev
|
||||
# Smoke test.
|
||||
python -c 'import sphinx; import sphinx_markdown_builder'
|
||||
python3 -c 'import sphinx; import sphinx_markdown_builder'
|
||||
|
||||
2
.github/actions/rust-toolchain/action.yml
vendored
2
.github/actions/rust-toolchain/action.yml
vendored
@@ -25,7 +25,7 @@ runs:
|
||||
set -x
|
||||
toolchain=$(
|
||||
case "$toolchain_channel" in
|
||||
(stable) echo 1.93 ;; # updatecli.d/rust.yml
|
||||
(stable) echo 1.96 ;; # updatecli.d/rust.yml
|
||||
(msrv) echo 1.85 ;; # updatecli.d/rust.yml
|
||||
(*)
|
||||
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"
|
||||
|
||||
2
.github/workflows/autolabel_prs.yml
vendored
2
.github/workflows/autolabel_prs.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- name: Set label and milestone
|
||||
id: set-label-milestone
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8, build_tools/update-dependencies.sh
|
||||
uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
script: |
|
||||
const completionsLabel = 'completions';
|
||||
|
||||
64
.github/workflows/build_docker_images.yml
vendored
64
.github/workflows/build_docker_images.yml
vendored
@@ -1,64 +0,0 @@
|
||||
name: Build Docker test images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'docker/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: docker-builds
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
NAMESPACE: fish-ci
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
if: github.repository_owner == 'fish-shell'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: alpine
|
||||
- os: ubuntu-latest
|
||||
target: ubuntu-oldest-supported
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
-
|
||||
name: Login to Container registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.NAMESPACE }}/${{ matrix.target }}
|
||||
flavor: |
|
||||
latest=true
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
context: docker/context
|
||||
push: true
|
||||
file: docker/${{ matrix.target }}.Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
2
.github/workflows/development-builds.yml
vendored
2
.github/workflows/development-builds.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
mkdir /tmp/fish-built
|
||||
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
|
||||
FISH_ARTEFACT_PATH=/tmp/fish-built DEB_SIGN_KEYFILE=/tmp/gpg/signing-gpg-key ./build_tools/make_linux_packages.sh $version
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
|
||||
with:
|
||||
name: linux-source-packages
|
||||
path: |
|
||||
|
||||
4
.github/workflows/lint-dependencies.yml
vendored
4
.github/workflows/lint-dependencies.yml
vendored
@@ -17,8 +17,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: EmbarkStudios/cargo-deny-action@44db170f6a7d12a6e90340e9e0fca1f650d34b14 # v2.0.15, build_tools/update-dependencies.sh
|
||||
- uses: EmbarkStudios/cargo-deny-action@8a45e1c7c9a95dfae3276e89a553705e40ae45a2 # v2.0.20, build_tools/update-dependencies.sh
|
||||
with:
|
||||
command: check licenses
|
||||
arguments: --all-features --locked --exclude-dev
|
||||
rust-version: 1.93 # updatecli.d/rust.yml
|
||||
rust-version: 1.96 # updatecli.d/rust.yml
|
||||
|
||||
23
.github/workflows/lint.yml
vendored
23
.github/workflows/lint.yml
vendored
@@ -22,6 +22,27 @@ jobs:
|
||||
- name: check rustfmt
|
||||
run: find build.rs crates src -type f -name '*.rs' | xargs rustfmt --check
|
||||
|
||||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@stable
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install shellcheck
|
||||
run: sudo apt install shellcheck
|
||||
- name: shellcheck
|
||||
run: cargo xtask shellcheck
|
||||
|
||||
po_files_up_to_date:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@stable
|
||||
- name: Install deps
|
||||
uses: ./.github/actions/install-dependencies
|
||||
- name: Check PO files
|
||||
run: cargo xtask gettext check
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -32,6 +53,8 @@ jobs:
|
||||
features: ""
|
||||
- rust_version: "stable"
|
||||
features: "--no-default-features"
|
||||
- rust_version: "stable"
|
||||
features: "--all-features"
|
||||
- rust_version: "msrv"
|
||||
features: ""
|
||||
steps:
|
||||
|
||||
2
.github/workflows/lockthreads.yml
vendored
2
.github/workflows/lockthreads.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
pull-requests: write # for dessant/lock-threads to lock PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@f5f995c727ac99a91dec92781a8e34e7c839a65e # v6.0.0, build_tools/update-dependencies.sh
|
||||
- uses: dessant/lock-threads@b2726a6ae6f1e1b06eb0ff28f7e4fb5e4246bbca # v6.0.2, build_tools/update-dependencies.sh
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '365'
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
sed -n 2p "$relnotes" | grep -q '^$'
|
||||
sed -i 1,2d "$relnotes"
|
||||
- name: Upload tarball artifact
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
|
||||
with:
|
||||
name: source-tarball
|
||||
path: |
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
|
||||
-C target/$arch-unknown-linux-musl/release fish
|
||||
done
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
|
||||
with:
|
||||
name: Static builds for Linux
|
||||
path: fish-${{ inputs.version }}-linux-*.tar.xz
|
||||
@@ -123,14 +123,14 @@ jobs:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0, build_tools/update-dependencies.sh
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1, build_tools/update-dependencies.sh
|
||||
with:
|
||||
merge-multiple: true
|
||||
path: /tmp/artifacts
|
||||
- name: List artifacts
|
||||
run: find /tmp/artifacts -type f
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0, build_tools/update-dependencies.sh
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
tag_name: ${{ inputs.version }}
|
||||
name: fish ${{ inputs.version }}
|
||||
|
||||
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
@@ -32,14 +32,6 @@ jobs:
|
||||
- name: make fish_run_tests
|
||||
run: |
|
||||
make -C build VERBOSE=1 fish_run_tests
|
||||
- name: translation updates
|
||||
run: |
|
||||
# Generate PO files. This should not result it a change in the repo if all translations are
|
||||
# up to date.
|
||||
# Ensure that fish is available as an executable.
|
||||
PATH="$PWD/build:$PATH" build_tools/update_translations.fish
|
||||
# Show diff output. Fail if there is any.
|
||||
git --no-pager diff --exit-code || { echo 'There are uncommitted changes after regenerating the gettext PO files. Make sure to update them via `build_tools/update_translations.fish` after changing source files.'; exit 1; }
|
||||
|
||||
ubuntu-32bit-static-pcre2:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -159,7 +151,7 @@ jobs:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0, build_tools/update-dependencies.sh
|
||||
- uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1, build_tools/update-dependencies.sh
|
||||
with:
|
||||
update: true
|
||||
msystem: MSYS
|
||||
@@ -167,7 +159,7 @@ jobs:
|
||||
- name: Install deps
|
||||
# Not using setup-msys2 `install` option to make it easier to copy/paste
|
||||
run: |
|
||||
pacman --noconfirm -S --needed git rust
|
||||
pacman --noconfirm -S --needed git rust python3 diffutils tmux
|
||||
- name: rebase
|
||||
env:
|
||||
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
|
||||
@@ -177,10 +169,8 @@ jobs:
|
||||
- name: cargo build
|
||||
run: |
|
||||
cargo build
|
||||
- name: smoketest
|
||||
# We can't run `build_tools/check.sh` yet, there are just too many failures
|
||||
# so this is just a quick check to make sure that fish can swim
|
||||
- name: tests
|
||||
env:
|
||||
FISH_CHECK_LINT: false
|
||||
run: |
|
||||
set -x
|
||||
[ "$(target/debug/fish.exe -c 'echo (math 1 + 1)')" = 2 ]
|
||||
cargo test
|
||||
cargo xtask check
|
||||
|
||||
53
.gitignore
vendored
53
.gitignore
vendored
@@ -20,7 +20,6 @@
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
!tests/*.out
|
||||
*.out
|
||||
*.pch
|
||||
*.slo
|
||||
@@ -36,46 +35,31 @@
|
||||
Desktop.ini
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
__pycache__/
|
||||
|
||||
.directory
|
||||
.fuse_hidden*
|
||||
|
||||
|
||||
# Directories that only contain transitory files from building and testing.
|
||||
/doc/
|
||||
/share/man/
|
||||
/share/doc/
|
||||
/test/
|
||||
/user_doc/
|
||||
|
||||
# File names that can appear in the project root that represent artifacts from
|
||||
# building and testing.
|
||||
/FISH-BUILD-VERSION-FILE
|
||||
/command_list.txt
|
||||
/command_list_toc.txt
|
||||
/compile_commands.json
|
||||
/doc.h
|
||||
# Artifacts from in-tree builds ("cmake .").
|
||||
/build.ninja
|
||||
/cargo/
|
||||
/CMakeCache.txt
|
||||
/CMakeFiles/
|
||||
/cmake_install.cmake
|
||||
/fish
|
||||
/fish.pc
|
||||
/fish_indent
|
||||
/fish_key_reader
|
||||
/fish_tests
|
||||
/lexicon.txt
|
||||
/lexicon_filter
|
||||
/toc.txt
|
||||
/version
|
||||
fish-build-version-witness.txt
|
||||
__pycache__
|
||||
/fish-localization-map-cache/
|
||||
/fish.pc
|
||||
/fish.pc.noversion
|
||||
/.ninja_log
|
||||
|
||||
# File names that can appear below the project root that represent artifacts
|
||||
# from building and testing.
|
||||
/doc_src/commands.hdr
|
||||
/doc_src/index.hdr
|
||||
/po/*.gmo
|
||||
/share/__fish_build_paths.fish
|
||||
/share/pkgconfig
|
||||
/tests/*.tmp.*
|
||||
/tests/.last-check-all-files
|
||||
/.venv/
|
||||
|
||||
# xcode
|
||||
## Build generated
|
||||
@@ -83,24 +67,19 @@ __pycache__
|
||||
*.xccheckout
|
||||
*.xcscmblueprin
|
||||
.vscode
|
||||
/DerivedData/
|
||||
/build/
|
||||
/DerivedData/
|
||||
/tags
|
||||
xcuserdata/
|
||||
/xcuserdata/
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
/target/
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by clangd
|
||||
/.cache
|
||||
/.cache/
|
||||
|
||||
# JetBrains editors.
|
||||
.idea/
|
||||
|
||||
@@ -1,33 +1,81 @@
|
||||
fish ?.?.? (released ???)
|
||||
=========================
|
||||
|
||||
Notable improvements and fixes
|
||||
------------------------------
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- `--command` and `--path` options in `complete` no longer unescape their value.
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
- On the first run after upgrading from an older version, fish will try harder to check if the current theme matches a historical default, in which case fish won't create ``~/.config/fish/conf.d/fish_frozen_theme.fish``.
|
||||
This means that on systems where fish version 3.x was installed originally, the update will avoid creating that file (:issue:`12725`).
|
||||
- ``fish_hg_prompt``, ``fish_git_prompt`` and ``fish_fossil_prompt`` now strip control characters from VCS state read off disk, matching ``prompt_pwd``.
|
||||
- The sample informative and minimalist prompts now use ``prompt_pwd`` instead of printing ``$PWD`` directly.
|
||||
- ``bind`` shows the file where bindings were defined (:issue:`12504`).
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
- With the exception of the ``$CMAKE_INSTALL_PREFIX/share/fish/man`` directory, fish no longer installs files to ``$CMAKE_INSTALL_PREFIX/share/fish``.
|
||||
In particular, this means that both
|
||||
``$CMAKE_INSTALL_PREFIX/share/fish/completions`` and
|
||||
``$CMAKE_INSTALL_PREFIX/share/fish/functions``
|
||||
should now be empty.
|
||||
If another package installs completions or functions to those directories,
|
||||
they should be changed to install to
|
||||
``extra_completionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_completions.d``) or
|
||||
``extra_functionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_functions.d``)
|
||||
instead.
|
||||
The old location has been ignored since fish 4.2.
|
||||
|
||||
Regression fixes:
|
||||
-----------------
|
||||
- (from 4.4.0) Vi mode ``c,W`` key binding wrongly deleted trailing spaces (:issue:`12790`).
|
||||
- (from 4.4.0) Vi mode ``x`` in :doc:`builtin read <cmds/read>` (:issue:`12724`).
|
||||
- (from 4.3.3) Repeated tab would sometimes insert smartcase completions redundantly.
|
||||
|
||||
fish 4.7.1 (released May 08, 2026)
|
||||
==================================
|
||||
|
||||
This release fixes a regression in 4.7.0 that caused the web config (``fish_config``) to fail to start (:issue:`12717`).
|
||||
|
||||
fish 4.7.0 (released May 05, 2026)
|
||||
==================================
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- The default theme (i.e. the ``fish_color_*`` variables) is no longer set in non-interactive shells.
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
- :doc:`prompt_pwd <cmds/prompt_pwd>` now strips control characters.
|
||||
- Background color and underline color specified in :envvar:`fish_color_valid_path` are now respected (:issue:`12622`).
|
||||
|
||||
Improved terminal support
|
||||
-------------------------
|
||||
- Repaint events (as triggered by changes to color variables or by event handlers running ``commandline -f repaint``) no longer reset the completion pager and other transient UI states (:issue:`12683`).
|
||||
- :envvar:`fish_color_valid_path` now respects background and underline colors (:issue:`12622`).
|
||||
- :doc:`funced <cmds/funced>` will no longer lose work if there are parse errors multiple times without new changes to the file.
|
||||
- Fixed a case where directory completions were sorted in a surprising order (:issue:`12695`).
|
||||
- When at the command token, the :kbd:`alt-o` binding will now open read-only files too (:issue:`12671`).
|
||||
- Private mode in-memory history (``set fish_history``) is no longer shared with :doc:`builtin read <cmds/read>` (:issue:`12662`).
|
||||
|
||||
Other improvements
|
||||
------------------
|
||||
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
|
||||
- :doc:`fish_update_completions <cmds/fish_update_completions>` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
|
||||
- Removing history entries via the :doc:`web-based config <cmds/fish_config>` is more intuitive.
|
||||
- If :envvar:`XDG_DATA_DIRS` is empty, the default value is assumed, which means that fish will now also use configuration from paths like ``$PREFIX/share/fish/vendor_completions.d`` (:issue:`11349`).
|
||||
- Some internal file descriptors were moved to number 10 or higher, to reduce risk of clashes with those used by the user in scripts.
|
||||
- The wording of error messages has been made consistent, especially for builtin subcommands (:issue:`12556`).
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
- When the default global config directory (``$PREFIX/etc/fish``) exists but has been overridden via ``-DCMAKE_INSTALL_SYSCONFDIR``, fish will now respect that override (:issue:`10748`).
|
||||
- ``build_tools/update_translations.fish`` has been replaced by ``cargo xtask gettext {check,new,update}`` (:issue:`12676`).
|
||||
- ``cargo xtask shellcheck`` to lint shell-scripts.
|
||||
|
||||
Regression fixes:
|
||||
-----------------
|
||||
- Vi mode ``dl`` (:issue:`12461`).
|
||||
- (from 4.6) Vi mode ``dl`` (:issue:`12461`).
|
||||
- (from 4.6) Backspace after newline (:issue:`12583`).
|
||||
- (from 4.3.3) Long options were spuriously completed after typing short options (85e76ba3561).
|
||||
- (from 3.2) ``nosuchcommand || echo hello`` executes the right hand side again (:issue:`12654`).
|
||||
|
||||
fish 4.6.0 (released March 28, 2026)
|
||||
====================================
|
||||
@@ -3354,7 +3402,7 @@ For distributors and developers
|
||||
standard sh instead.
|
||||
- The ``hostname`` command is no longer required for fish to operate.
|
||||
|
||||
–
|
||||
-
|
||||
|
||||
fish 2.7.1 (released December 23, 2017)
|
||||
=======================================
|
||||
@@ -3366,7 +3414,7 @@ session (:issue:`4521`).
|
||||
If you are upgrading from version 2.6.0 or before, please also review
|
||||
the release notes for 2.7.0 and 2.7b1 (included below).
|
||||
|
||||
–
|
||||
-
|
||||
|
||||
fish 2.7.0 (released November 23, 2017)
|
||||
=======================================
|
||||
@@ -3378,7 +3426,7 @@ from version 2.6.0 or before, please also review the release notes for
|
||||
Xcode builds and macOS packages could not be produced with 2.7b1, but
|
||||
this is fixed in 2.7.0.
|
||||
|
||||
–
|
||||
-
|
||||
|
||||
fish 2.7b1 (released October 31, 2017)
|
||||
======================================
|
||||
@@ -4079,7 +4127,7 @@ Other notable fixes and improvements
|
||||
- Add support for bright colors (:issue:`1464`)
|
||||
- Allow Ctrl-J (``\cj``) to be bound separately from Ctrl-M
|
||||
(``\cm``) (:issue:`217`)
|
||||
- psub now has a “-s”/“–suffix” option to name the temporary file with
|
||||
- psub now has a “-s”/“-suffix” option to name the temporary file with
|
||||
that suffix
|
||||
- Enable 24-bit colors on select terminals (:issue:`2495`)
|
||||
- Support for SVN status in the prompt (:issue:`2582`)
|
||||
@@ -4469,7 +4517,7 @@ Other Notable Fixes
|
||||
- xsel is no longer built as part of fish. It will still be invoked if
|
||||
installed separately :issue:`633`
|
||||
- \__fish_filter_mime no longer spews :issue:`628`
|
||||
- The –no-execute option to fish no longer falls over when reaching the
|
||||
- The -no-execute option to fish no longer falls over when reaching the
|
||||
end of a block :issue:`624`
|
||||
- fish_config knows how to find fish even if it’s not in the $PATH :issue:`621`
|
||||
- A leading space now prevents writing to history, as is done in bash
|
||||
|
||||
@@ -271,13 +271,14 @@ Adding translations for a new language
|
||||
--------------------------------------
|
||||
|
||||
Creating new translations requires the Gettext tools.
|
||||
More specifically, you will need ``msguniq`` and ``msgmerge`` for creating translations for a new
|
||||
language.
|
||||
To create a new translation, run::
|
||||
More specifically, you will need ``msguniq``, ``msgmerge``, and ``msgattrib``
|
||||
for creating translations for a new language.
|
||||
To create a PO file for a new language ``ll_CC``, run::
|
||||
|
||||
build_tools/update_translations.fish localization/po/ll_CC.po
|
||||
cargo xtask gettext new ll_CC
|
||||
|
||||
This will create a new PO file containing all messages available for translation.
|
||||
This will create a new PO file in ``localization/po/``
|
||||
containing all messages available for translation.
|
||||
If the file already exists, it will be updated.
|
||||
|
||||
After modifying a PO file, you can recompile fish, and it will integrate the modifications you made.
|
||||
@@ -347,10 +348,12 @@ Modifications to strings in source files
|
||||
----------------------------------------
|
||||
|
||||
If a string changes in the sources, the old translations will no longer work.
|
||||
They will be preserved in the PO files, but commented-out (starting with ``#~``).
|
||||
If you add/remove/change a translatable strings in a source file,
|
||||
run ``build_tools/update_translations.fish`` to propagate this to all translation files (``localization/po/*.po``).
|
||||
run ``cargo xtask gettext update`` to propagate this to all translation files (``localization/po/*.po``).
|
||||
This is only relevant for developers modifying the source files of fish or fish scripts.
|
||||
Note translations for messages which are no longer present in the sources will be deleted from the PO files.
|
||||
If the source string changed in a way which should not affect translations,
|
||||
consider updating the ``msgid`` in the PO files such that translations are preserved.
|
||||
|
||||
Setting Code Up For Translations
|
||||
--------------------------------
|
||||
|
||||
632
Cargo.lock
generated
632
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
79
Cargo.toml
79
Cargo.toml
@@ -2,9 +2,9 @@
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
# To build revisions that use Corrosion (those before 2024-01), use CMake 3.19, Rustc 1.78 and Rustup 1.27.
|
||||
rust-version = "1.85"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/fish-shell/fish-shell"
|
||||
# see doc_src/license.rst for details
|
||||
# don't forget to update COPYING and debian/copyright too
|
||||
@@ -12,11 +12,13 @@ license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
anstyle = "1.0.13"
|
||||
anyhow = "1.0.102"
|
||||
assert_matches = "1.5.0"
|
||||
bitflags = "2.5.0"
|
||||
cc = "1.0.94"
|
||||
cfg-if = "1.0.3"
|
||||
clap = { version = "4.5.54", features = ["derive"] }
|
||||
clap_complete = { version = "4.6.4", features = ["unstable-dynamic"] }
|
||||
errno = "0.3.0"
|
||||
fish-build-helper = { path = "crates/build-helper" }
|
||||
fish-build-man-pages = { path = "crates/build-man-pages" }
|
||||
@@ -32,53 +34,54 @@ fish-printf = { path = "crates/printf", features = ["widestring"] }
|
||||
fish-tempfile = { path = "crates/tempfile" }
|
||||
fish-util = { path = "crates/util" }
|
||||
fish-wcstringutil = { path = "crates/wcstringutil" }
|
||||
fish-wgetopt = { path = "crates/wgetopt" }
|
||||
fish-widecharwidth = { path = "crates/widecharwidth" }
|
||||
fish-widestring = { path = "crates/widestring" }
|
||||
fish-wgetopt = { path = "crates/wgetopt" }
|
||||
ignore = "0.4.25"
|
||||
itertools = "0.14.0"
|
||||
libc = "0.2.177"
|
||||
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
|
||||
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
|
||||
# files as of 22 April 2024.
|
||||
lru = "0.16.2"
|
||||
lru = "0.18.0"
|
||||
nix = { version = "0.31.1", default-features = false, features = [
|
||||
"event",
|
||||
"fs",
|
||||
"inotify",
|
||||
"hostname",
|
||||
"resource",
|
||||
"process",
|
||||
"signal",
|
||||
"term",
|
||||
"user",
|
||||
"event",
|
||||
"fs",
|
||||
"hostname",
|
||||
"inotify",
|
||||
"process",
|
||||
"resource",
|
||||
"signal",
|
||||
"term",
|
||||
"user",
|
||||
] }
|
||||
num-traits = "0.2.19"
|
||||
once_cell = "1.19.0"
|
||||
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
|
||||
"utf32",
|
||||
"utf32",
|
||||
] }
|
||||
phf = { version = "0.13", default-features = false }
|
||||
phf_codegen = "0.13"
|
||||
portable-atomic = { version = "1", default-features = false, features = [
|
||||
"fallback",
|
||||
"fallback",
|
||||
] }
|
||||
proc-macro2 = "1.0"
|
||||
rand = { version = "0.9.2", default-features = false, features = [
|
||||
"small_rng",
|
||||
"thread_rng",
|
||||
] }
|
||||
rand = { version = "0.10.1", default-features = false, features = ["thread_rng"] }
|
||||
regex = "1.12.3"
|
||||
rsconf = "0.3.0"
|
||||
rust-embed = { version = "8.11.0", features = [
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
rustc_version = "0.4.1"
|
||||
serial_test = { version = "3", default-features = false }
|
||||
widestring = "1.2.0"
|
||||
strum_macros = "0.28.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-width = "0.2.0"
|
||||
unix_path = "1.0.1"
|
||||
walkdir = "2.5.0"
|
||||
widestring = "1.2.0"
|
||||
xterm-color = "1.0.1"
|
||||
|
||||
[profile.release]
|
||||
@@ -91,7 +94,7 @@ debug = true
|
||||
|
||||
[package]
|
||||
name = "fish"
|
||||
version = "4.6.0"
|
||||
version = "4.7.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
default-run = "fish"
|
||||
@@ -128,6 +131,7 @@ num-traits.workspace = true
|
||||
once_cell.workspace = true
|
||||
pcre2.workspace = true
|
||||
rand.workspace = true
|
||||
strum_macros.workspace = true
|
||||
xterm-color.workspace = true
|
||||
|
||||
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
|
||||
@@ -135,16 +139,16 @@ portable-atomic.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
rust-embed = { workspace = true, features = [
|
||||
"deterministic-timestamps",
|
||||
"debug-embed",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
"deterministic-timestamps",
|
||||
"debug-embed",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
rust-embed = { workspace = true, features = [
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -156,6 +160,7 @@ fish-build-helper.workspace = true
|
||||
fish-gettext-mo-file-parser.workspace = true
|
||||
phf_codegen = { workspace = true, optional = true }
|
||||
rsconf.workspace = true
|
||||
rustc_version.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
unix_path.workspace = true
|
||||
@@ -180,21 +185,19 @@ path = "src/bin/fish_key_reader.rs"
|
||||
default = ["embed-manpages", "localize-messages"]
|
||||
benchmark = []
|
||||
embed-manpages = ["dep:fish-build-man-pages"]
|
||||
# This feature is used to enable extracting messages from the source code for localization.
|
||||
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
|
||||
# desired. This happens for the `gettext` xtask, which is also invoked via `cargo xtask check`.
|
||||
# There should not be a need to enable this feature manually.
|
||||
gettext-extract = ["dep:fish-gettext-extraction"]
|
||||
# Enable gettext localization at runtime. Requires the `msgfmt` tool to generate catalog data at
|
||||
# build time.
|
||||
localize-messages = ["dep:fish-gettext"]
|
||||
# This feature is used to enable extracting messages from the source code for localization.
|
||||
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
|
||||
# desired. This happens when running tests via `cargo xtask check` and when calling
|
||||
# `build_tools/update_translations.fish`, so there should not be a need to enable it manually.
|
||||
gettext-extract = ["dep:fish-gettext-extraction"]
|
||||
|
||||
# The following features are auto-detected by the build-script and should not be enabled manually.
|
||||
tsan = []
|
||||
|
||||
[workspace.lints]
|
||||
rust.non_camel_case_types = "allow"
|
||||
rust.non_upper_case_globals = "allow"
|
||||
rust.unknown_lints = { level = "allow", priority = -1 }
|
||||
rust.unstable_name_collisions = "allow"
|
||||
rustdoc.private_intra_doc_links = "allow"
|
||||
@@ -227,6 +230,8 @@ unused_trait_names = "warn"
|
||||
# In the future, they might change to flag other methods of printing.
|
||||
print_stdout = "deny"
|
||||
print_stderr = "deny"
|
||||
# usage in tests is fine since it avoids interacting with TopicMonitor
|
||||
# and is configured in clippy.toml with `allow-print-in-tests`
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
14
README.rst
14
README.rst
@@ -1,9 +1,5 @@
|
||||
.. |Cirrus CI| image:: https://api.cirrus-ci.com/github/fish-shell/fish-shell.svg?branch=master
|
||||
:target: https://cirrus-ci.com/github/fish-shell/fish-shell
|
||||
:alt: Cirrus CI Build Status
|
||||
|
||||
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status| |Cirrus CI|
|
||||
=============================================================================================
|
||||
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status|
|
||||
=================================================================================
|
||||
|
||||
fish is a smart and user-friendly command line shell for macOS, Linux,
|
||||
and the rest of the family. fish includes features like syntax
|
||||
@@ -31,7 +27,7 @@ macOS
|
||||
|
||||
fish can be installed:
|
||||
|
||||
- using `Homebrew <http://brew.sh/>`__: ``brew install fish``
|
||||
- using `Homebrew <https://brew.sh/>`__: ``brew install fish``
|
||||
- using `MacPorts <https://www.macports.org/>`__:
|
||||
``sudo port install fish``
|
||||
- using the `installer from fishshell.com <https://fishshell.com/>`__
|
||||
@@ -221,5 +217,5 @@ There is also a fish tag on Stackoverflow, but it is typically a poor fit.
|
||||
Found a bug? Have an awesome idea? Please `open an
|
||||
issue <https://github.com/fish-shell/fish-shell/issues/new>`__.
|
||||
|
||||
.. |Build Status| image:: https://github.com/fish-shell/fish-shell/workflows/make%20test/badge.svg
|
||||
:target: https://github.com/fish-shell/fish-shell/actions
|
||||
.. |Build Status| image:: https://github.com/fish-shell/fish-shell/actions/workflows/test.yml/badge.svg
|
||||
:target: https://github.com/fish-shell/fish-shell/actions/workflows/test.yml
|
||||
|
||||
4
build.rs
4
build.rs
@@ -6,6 +6,10 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn main() {
|
||||
let is_nightly =
|
||||
rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly;
|
||||
rsconf::declare_cfg("nightly", is_nightly);
|
||||
|
||||
setup_paths();
|
||||
|
||||
// Add our default to enable tools that don't go through CMake, like "cargo test" and the
|
||||
|
||||
@@ -60,7 +60,7 @@ cargo() {
|
||||
fi
|
||||
}
|
||||
|
||||
# shellcheck disable=2329
|
||||
# shellcheck disable=2317,2329
|
||||
cleanup () {
|
||||
if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then
|
||||
rm -r "$gettext_template_dir"
|
||||
@@ -98,10 +98,17 @@ if $lint; then
|
||||
if command -v cargo-deny >/dev/null; then
|
||||
cargo deny --all-features --locked --exclude-dev check licenses
|
||||
fi
|
||||
|
||||
if command -v shellcheck >/dev/null || { test -n "$CI" && ! $is_cygwin; }; then
|
||||
cargo xtask shellcheck
|
||||
fi
|
||||
|
||||
PATH="$build_dir:$PATH" cargo xtask format --all --check
|
||||
for features in "" --no-default-features; do
|
||||
for features in "" --no-default-features --all-features; do
|
||||
cargo clippy --workspace --all-targets $features
|
||||
done
|
||||
|
||||
cargo xtask gettext --rust-extraction-dir="$gettext_template_dir" check
|
||||
fi
|
||||
|
||||
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
|
||||
@@ -125,23 +132,26 @@ if $lint; then
|
||||
cargo doc --workspace --no-deps
|
||||
fi
|
||||
|
||||
# Using "()" not "{}" because we do want a subshell (for the export)
|
||||
system_tests() (
|
||||
# shellcheck disable=2163
|
||||
[ -n "$*" ] && export "$@"
|
||||
# shellcheck disable=2031
|
||||
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
|
||||
"$workspace_root/tests/test_driver.py" "$build_dir"
|
||||
)
|
||||
system_tests() {
|
||||
"$workspace_root/tests/test_driver.py" "$build_dir" "$@"
|
||||
}
|
||||
|
||||
if $is_cygwin; then
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=winsymlinks
|
||||
(
|
||||
export "$cygwin_var"=winsymlinks
|
||||
system_tests
|
||||
)
|
||||
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=
|
||||
(
|
||||
# Only redo the tests that use `ln` to saves some time
|
||||
export "$cygwin_var"=
|
||||
# shellcheck disable=2046
|
||||
system_tests $(grep -l -E '\bln\b' -r tests/checks/)
|
||||
)
|
||||
else
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests${reset}\n"
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
#
|
||||
# Tool to generate gettext messages template file.
|
||||
# Writes to stdout.
|
||||
# Intended to be called from `update_translations.fish`.
|
||||
|
||||
argparse use-existing-template= -- $argv
|
||||
or exit $status
|
||||
|
||||
begin
|
||||
# Write header. This is required by msguniq.
|
||||
# Note that this results in the file being overwritten.
|
||||
# This is desired behavior, to get rid of the results of prior invocations
|
||||
# of this script.
|
||||
set -l header 'msgid ""\nmsgstr "Content-Type: text/plain; charset=UTF-8\\\\n"\n\n'
|
||||
printf $header
|
||||
|
||||
set -g workspace_root (path resolve (status dirname)/..)
|
||||
|
||||
set -l rust_extraction_dir
|
||||
if set -l --query _flag_use_existing_template
|
||||
set rust_extraction_dir $_flag_use_existing_template
|
||||
else
|
||||
set rust_extraction_dir (mktemp -d)
|
||||
# We need to build to ensure that the proc macro for extracting strings runs.
|
||||
FISH_GETTEXT_EXTRACTION_DIR=$rust_extraction_dir cargo check --features=gettext-extract
|
||||
or exit 1
|
||||
end
|
||||
|
||||
function mark_section
|
||||
set -l section_name $argv[1]
|
||||
echo 'msgid "fish-section-'$section_name'"'
|
||||
echo 'msgstr ""'
|
||||
echo ''
|
||||
end
|
||||
|
||||
mark_section tier1-from-rust
|
||||
|
||||
# Get rid of duplicates and sort.
|
||||
begin
|
||||
# Without providing this header, msguniq complains when a msgid is non-ASCII.
|
||||
printf $header
|
||||
find $rust_extraction_dir -type f -exec cat {} +
|
||||
end |
|
||||
msguniq --no-wrap --sort-output |
|
||||
# Remove the header again. Otherwise it would appear twice, breaking the msguniq at the end
|
||||
# of this file.
|
||||
sed '/^msgid ""$/ {N; /\nmsgstr "Content-Type: text\/plain; charset=UTF-8\\\\n"$/ {N; d}}'
|
||||
|
||||
if not set -l --query _flag_use_existing_template
|
||||
rm -r $rust_extraction_dir
|
||||
end
|
||||
|
||||
function extract_fish_script_messages_impl
|
||||
set -l regex $argv[1]
|
||||
set -e argv[1]
|
||||
# Using xgettext causes more trouble than it helps.
|
||||
# This is due to handling of escaping in fish differing from formats xgettext understands
|
||||
# (e.g. POSIX shell strings).
|
||||
# We work around this issue by manually writing the file content.
|
||||
|
||||
# Steps:
|
||||
# 1. We extract strings to be translated from the relevant files and drop the rest. This step
|
||||
# depends on the regex matching the entire line, and the first capture group matching the
|
||||
# string.
|
||||
# 2. We unescape. This gets rid of some escaping necessary in fish strings.
|
||||
# 3. The resulting strings are sorted alphabetically. This step is optional. Not sorting would
|
||||
# result in strings from the same file appearing together. Removing duplicates is also
|
||||
# optional, since msguniq takes care of that later on as well.
|
||||
# 4. Single backslashes are replaced by double backslashes. This results in the backslashes
|
||||
# being interpreted as literal backslashes by gettext tooling.
|
||||
# 5. Double quotes are escaped, such that they are not interpreted as the start or end of
|
||||
# a msgid.
|
||||
# 6. We transform the string into the format expected in a PO file.
|
||||
cat $argv |
|
||||
string replace --filter --regex $regex '$1' |
|
||||
string unescape |
|
||||
sort -u |
|
||||
sed -E -e 's_\\\\_\\\\\\\\_g' -e 's_"_\\\\"_g' -e 's_^(.*)$_msgid "\1"\nmsgstr ""\n_'
|
||||
end
|
||||
|
||||
function extract_fish_script_messages
|
||||
set -l tier $argv[1]
|
||||
set -e argv[1]
|
||||
if not set -q argv[1]
|
||||
return
|
||||
end
|
||||
# This regex handles explicit requests to translate a message. These are more important to translate
|
||||
# than messages which should be implicitly translated.
|
||||
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
|
||||
mark_section "$tier-from-script-explicitly-added"
|
||||
extract_fish_script_messages_impl $explicit_regex $argv
|
||||
|
||||
# This regex handles descriptions for `complete` and `function` statements. These messages are not
|
||||
# particularly important to translate. Hence the "implicit" label.
|
||||
set -l implicit_regex '^(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
|
||||
mark_section "$tier-from-script-implicitly-added"
|
||||
extract_fish_script_messages_impl $implicit_regex $argv
|
||||
end
|
||||
|
||||
set -g share_dir $workspace_root/share
|
||||
|
||||
set -l tier1 $share_dir/config.fish
|
||||
set -l tier2
|
||||
set -l tier3
|
||||
|
||||
for file in $share_dir/completions/*.fish $share_dir/functions/*.fish
|
||||
# set -l tier (string match -r '^# localization: .*' <$file)
|
||||
set -l tier (string replace -rf -m1 \
|
||||
'^# localization: (.*)$' '$1' <$file)
|
||||
if set -q tier[1]
|
||||
switch "$tier"
|
||||
case tier1 tier2 tier3
|
||||
set -a $tier $file
|
||||
case 'skip*'
|
||||
case '*'
|
||||
echo >&2 "$file:1 unexpected localization tier: $tier"
|
||||
exit 1
|
||||
end
|
||||
continue
|
||||
end
|
||||
set -l dirname (path basename (path dirname $file))
|
||||
set -l command_name (path basename --no-extension $file)
|
||||
if test $dirname = functions &&
|
||||
string match -q -- 'fish_*' $command_name
|
||||
set -a tier1 $file
|
||||
continue
|
||||
end
|
||||
if test $dirname != completions
|
||||
echo >&2 "$file:1 missing localization tier for function file"
|
||||
exit 1
|
||||
end
|
||||
if test -e $workspace_root/doc_src/cmds/$command_name.rst
|
||||
set -a tier1 $file
|
||||
else
|
||||
set -a tier3 $file
|
||||
end
|
||||
end
|
||||
|
||||
extract_fish_script_messages tier1 $tier1
|
||||
extract_fish_script_messages tier2 $tier2
|
||||
extract_fish_script_messages tier3 $tier3
|
||||
end |
|
||||
# At this point, all extracted strings have been written to stdout,
|
||||
# starting with the ones taken from the Rust sources,
|
||||
# followed by strings explicitly marked for translation in fish scripts,
|
||||
# and finally the strings from fish scripts which get translated implicitly.
|
||||
# Because we do not eliminate duplicates across these categories,
|
||||
# we do it here, since other gettext tools expect no duplicates.
|
||||
msguniq --no-wrap
|
||||
@@ -16,7 +16,7 @@ manifest=$tmpdir/Cargo.toml
|
||||
lockfile=$tmpdir/Cargo.lock
|
||||
|
||||
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml >"$manifest"
|
||||
awk -v version=$VERSION '
|
||||
awk -v version="$VERSION" '
|
||||
/^name = "fish"$/ { ok=1 }
|
||||
ok == 1 && /^version = ".*"$/ {
|
||||
ok = 2;
|
||||
|
||||
@@ -48,7 +48,7 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
|
||||
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
|
||||
printf %s \
|
||||
"This release brings $num_commits new commits since $previous_version," \
|
||||
" contributed by $num_authors authors, $num_new_authors of which are new committers."
|
||||
" contributed by $num_authors authors, $num_new_authors of which are new faces."
|
||||
echo
|
||||
echo
|
||||
)
|
||||
@@ -86,10 +86,13 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
|
||||
echo
|
||||
echo 'Download links:'
|
||||
echo 'To download the source code for fish, we suggest the file named ``fish-'"$version"'.tar.xz``.'
|
||||
# shellcheck disable=2016
|
||||
echo 'The file downloaded from ``Source code (tar.gz)`` will not build correctly.'
|
||||
# shellcheck disable=2016
|
||||
echo 'A GPG signature using `this key <'"${FISH_GPG_PUBLIC_KEY_URL:-???}"'>`__ is available as ``fish-'"$version"'.tar.xz.asc``.'
|
||||
echo
|
||||
echo 'The files called ``fish-'"$version"'-linux-*.tar.xz`` contain'
|
||||
# shellcheck disable=2016
|
||||
echo '`standalone fish binaries <https://github.com/fish-shell/fish-shell/?tab=readme-ov-file#building-fish-with-cargo>`__'
|
||||
echo 'for any Linux with the given CPU architecture.'
|
||||
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst
|
||||
|
||||
@@ -72,7 +72,7 @@ integration_branch=$(
|
||||
--format='%(refname:strip=2)'
|
||||
)
|
||||
[ -n "$integration_branch" ] ||
|
||||
git merge-base --is-ancestor $remote/master HEAD
|
||||
git merge-base --is-ancestor "$remote"/master HEAD
|
||||
|
||||
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
|
||||
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
|
||||
@@ -113,9 +113,9 @@ CreateCommit "Release $version"
|
||||
# Tags must be full objects, not lightweight tags, for
|
||||
# git_version-gen.sh to work.
|
||||
git -c "user.signingKey=$committer" \
|
||||
tag --sign --message="Release $version" $version
|
||||
tag --sign --message="Release $version" "$version"
|
||||
|
||||
git push $remote $version
|
||||
git push "$remote" "$version"
|
||||
|
||||
TIMEOUT=
|
||||
gh() {
|
||||
@@ -173,6 +173,7 @@ actual_tag_oid=$(git ls-remote "$remote" |
|
||||
(
|
||||
cd "$tmpdir/local-tarball/fish-$version"
|
||||
uv --no-managed-python venv
|
||||
# shellcheck disable=1091
|
||||
. .venv/bin/activate
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug .
|
||||
ninja doc
|
||||
@@ -180,14 +181,17 @@ actual_tag_oid=$(git ls-remote "$remote" |
|
||||
CopyDocs() {
|
||||
rm -rf "$fish_site/site/docs/$1"
|
||||
cp -r "$tmpdir/local-tarball/fish-$version/cargo/fish-docs/html" "$fish_site/site/docs/$1"
|
||||
git -C $fish_site add "site/docs/$1"
|
||||
git -C "$fish_site" add "site/docs/$1"
|
||||
}
|
||||
minor_version=${version%.*}
|
||||
CopyDocs "$minor_version"
|
||||
latest_release=$(
|
||||
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
|
||||
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
|
||||
's/-/~/g' | LC_ALL=C sort --version-sort)
|
||||
sed '
|
||||
# De-prioritize release candidates (1.2.3-rc0)
|
||||
s/-/~/g
|
||||
' | LC_ALL=C sort --version-sort
|
||||
)
|
||||
printf %s\\n "$releases" | tail -1
|
||||
)
|
||||
if [ "$version" = "$latest_release" ]; then
|
||||
@@ -251,6 +255,28 @@ do
|
||||
sleep 20
|
||||
done
|
||||
|
||||
milestone_version="$(
|
||||
if echo "$version" | grep -q '\.0$'; then
|
||||
echo "$minor_version"
|
||||
else
|
||||
echo "$version"
|
||||
fi
|
||||
)"
|
||||
milestone_number() {
|
||||
gh_api_repo milestones?state=open |
|
||||
jq --arg name "fish $1" '
|
||||
.[] | select(.title == $name) | .number
|
||||
'
|
||||
}
|
||||
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
|
||||
--method PATCH --raw-field state=closed
|
||||
next_minor_version=$(echo "$minor_version" |
|
||||
awk -F. '{ printf "%s.%s", $1, $2+1 }')
|
||||
if [ -z "$(milestone_number "$next_minor_version")" ]; then
|
||||
gh_api_repo milestones --method POST \
|
||||
--raw-field title="fish $next_minor_version"
|
||||
fi
|
||||
|
||||
(
|
||||
cd "$fish_site"
|
||||
make new-release
|
||||
@@ -272,7 +298,7 @@ done
|
||||
)
|
||||
|
||||
if [ -n "$integration_branch" ]; then {
|
||||
git push $remote "$version^{commit}":refs/heads/$integration_branch
|
||||
git push "$remote" "$version^{commit}:refs/heads/$integration_branch"
|
||||
} else {
|
||||
changelog=$(cat - CHANGELOG.rst <<EOF
|
||||
fish ?.?.? (released ???)
|
||||
@@ -283,32 +309,9 @@ EOF
|
||||
printf %s\\n "$changelog" >CHANGELOG.rst
|
||||
git add CHANGELOG.rst
|
||||
CreateCommit "start new cycle"
|
||||
git push $remote HEAD:master
|
||||
git push "$remote" HEAD:master
|
||||
} fi
|
||||
|
||||
milestone_version="$(
|
||||
if echo "$version" | grep -q '\.0$'; then
|
||||
echo "$minor_version"
|
||||
else
|
||||
echo "$version"
|
||||
fi
|
||||
)"
|
||||
milestone_number() {
|
||||
gh_api_repo milestones?state=open |
|
||||
jq --arg name "fish $1" '
|
||||
.[] | select(.title == $name) | .number
|
||||
'
|
||||
}
|
||||
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
|
||||
--method PATCH --raw-field state=closed
|
||||
|
||||
next_minor_version=$(echo "$minor_version" |
|
||||
awk -F. '{ printf "%s.%s", $1, $2+1 }')
|
||||
if [ -z "$(milestone_number "$next_minor_version")" ]; then
|
||||
gh_api_repo milestones --method POST \
|
||||
--raw-field title="fish $next_minor_version"
|
||||
fi
|
||||
|
||||
exit
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
set -ex
|
||||
|
||||
command -v curl
|
||||
command -v gcloud
|
||||
command -v jq
|
||||
command -v rustup
|
||||
command -v updatecli
|
||||
@@ -42,17 +41,18 @@ uv lock --upgrade --exclude-newer="$(date --date='7 days ago' --iso-8601)"
|
||||
|
||||
from_gh() {
|
||||
repo=$1
|
||||
path=$2
|
||||
destination=$3
|
||||
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/master/"${path}")
|
||||
branch=$2
|
||||
path=$3
|
||||
destination=$4
|
||||
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/"${branch}"/"${path}")
|
||||
printf '%s\n' "$contents" >"$destination"
|
||||
}
|
||||
|
||||
from_gh ridiculousfish/widecharwidth widechar_width.rs crates/widecharwidth/src/widechar_width.rs
|
||||
from_gh ridiculousfish/littlecheck littlecheck/littlecheck.py tests/littlecheck.py
|
||||
from_gh catppuccin/fish themes/catppuccin-frappe.theme share/themes/catppuccin-frappe.theme
|
||||
from_gh catppuccin/fish themes/catppuccin-macchiato.theme share/themes/catppuccin-macchiato.theme
|
||||
from_gh catppuccin/fish themes/catppuccin-mocha.theme share/themes/catppuccin-mocha.theme
|
||||
from_gh ridiculousfish/widecharwidth master widechar_width.rs crates/widecharwidth/src/widechar_width.rs
|
||||
from_gh ridiculousfish/littlecheck master littlecheck/littlecheck.py tests/littlecheck.py
|
||||
from_gh catppuccin/fish main themes/catppuccin-frappe.theme share/themes/catppuccin-frappe.theme
|
||||
from_gh catppuccin/fish main themes/catppuccin-macchiato.theme share/themes/catppuccin-macchiato.theme
|
||||
from_gh catppuccin/fish main themes/catppuccin-mocha.theme share/themes/catppuccin-mocha.theme
|
||||
|
||||
# Update Cargo.lock
|
||||
cargo update
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
# Updates the files used for gettext translations.
|
||||
# By default, the whole xgettext + msgmerge pipeline runs,
|
||||
# which extracts the messages from the source files into $template_file,
|
||||
# and updates the PO files for each language from that.
|
||||
#
|
||||
# Use cases:
|
||||
# For developers:
|
||||
# - Run with no args to update all PO files after making changes to Rust/fish sources.
|
||||
# For translators:
|
||||
# - Specify the language you want to work on as an argument, which must be a file in the
|
||||
# localization/po/ directory. You can specify a language which does not have translations
|
||||
# yet by specifying the name of a file which does not yet exist.
|
||||
# Make sure to follow the naming convention.
|
||||
# For testing:
|
||||
# - Specify `--dry-run` to see if any updates to the PO files would by applied by this script.
|
||||
# If this flag is specified, the script will exit with an error if there are outstanding
|
||||
# changes, and will display the diff. Do not specify other flags if `--dry-run` is specified.
|
||||
#
|
||||
# Specify `--use-existing-template=DIR` to prevent running cargo for extracting an up-to-date
|
||||
# version of the localized strings. This flag is intended for testing setups which make it
|
||||
# inconvenient to run cargo here, but run it in an earlier step to ensure up-to-date values.
|
||||
# This argument is passed on to the `fish_xgettext.fish` script and has no other uses.
|
||||
# `DIR` must be the path to a gettext template file generated from our compilation process.
|
||||
# It can be obtained by running:
|
||||
# set -l DIR (mktemp -d)
|
||||
# FISH_GETTEXT_EXTRACTION_DIR=$DIR cargo check --features=gettext-extract
|
||||
|
||||
# The sort utility is locale-sensitive.
|
||||
# Ensure that sorting output is consistent by setting LC_ALL here.
|
||||
set -gx LC_ALL C.UTF-8
|
||||
|
||||
set -l build_tools (status dirname)
|
||||
set -l po_dir $build_tools/../localization/po
|
||||
|
||||
set -l extract
|
||||
|
||||
argparse dry-run use-existing-template= -- $argv
|
||||
or exit $status
|
||||
|
||||
if test -z $argv[1]
|
||||
# Update everything if not specified otherwise.
|
||||
set -g po_files $po_dir/*.po
|
||||
else
|
||||
set -l po_dir_id (stat --format='%d:%i' -- $po_dir)
|
||||
for arg in $argv
|
||||
set -l arg_dir_id (stat --format='%d:%i' -- (dirname $arg) 2>/dev/null)
|
||||
if test $po_dir_id != "$arg_dir_id"
|
||||
echo "Argument $arg is not a file in the directory $(realpath $po_dir)."
|
||||
echo "Non-option arguments must specify paths to files in this directory."
|
||||
echo ""
|
||||
echo "If you want to add a new language to the translations not the following:"
|
||||
echo "The filename must identify a language, with a two letter ISO 639-1 language code of the target language (e.g. 'pt' for Portuguese), and use the file extension '.po'."
|
||||
echo "Optionally, you can specify a regional variant (e.g. 'pt_BR')."
|
||||
echo "So valid filenames are of the shape 'll.po' or 'll_CC.po'."
|
||||
exit 1
|
||||
end
|
||||
if not basename $arg | grep -qE '^[a-z]{2,3}(_[A-Z]{2})?\.po$'
|
||||
echo "Filename does not match the expected format ('ll.po' or 'll_CC.po')."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
set -g po_files $argv
|
||||
end
|
||||
|
||||
set -g template_file (mktemp)
|
||||
# Protect from externally set $tmpdir leaking into this script.
|
||||
set -g tmpdir
|
||||
|
||||
function cleanup_exit
|
||||
set -l exit_status $status
|
||||
|
||||
rm $template_file
|
||||
|
||||
if set -g --query tmpdir[1]
|
||||
rm -r $tmpdir
|
||||
end
|
||||
|
||||
exit $exit_status
|
||||
end
|
||||
|
||||
if set -l --query extract
|
||||
set -l xgettext_args
|
||||
if set -l --query _flag_use_existing_template
|
||||
set xgettext_args --use-existing-template=$_flag_use_existing_template
|
||||
end
|
||||
$build_tools/fish_xgettext.fish $xgettext_args >$template_file
|
||||
or cleanup_exit
|
||||
end
|
||||
|
||||
if set -l --query _flag_dry_run
|
||||
# On a dry run, we do not modify localization/po/ but write to a temporary directory instead
|
||||
# and check if there is a difference between localization/po/ and the tmpdir after re-generating
|
||||
# the PO files.
|
||||
set -g tmpdir (mktemp -d)
|
||||
|
||||
# Ensure tmpdir has the same initial state as the po dir.
|
||||
cp -r $po_dir/* $tmpdir
|
||||
end
|
||||
|
||||
# This is used to identify lines which should be set here via $header_lines.
|
||||
# Make sure that this prefix does not appear elsewhere in the file and only contains characters
|
||||
# without special meaning in a sed pattern.
|
||||
set -g header_prefix "# fish-note-sections: "
|
||||
|
||||
function print_header
|
||||
set -l header_lines \
|
||||
"Translations are divided into sections, each starting with a fish-section-* pseudo-message." \
|
||||
"The first few sections are more important." \
|
||||
"Ignore the tier3 sections unless you have a lot of time."
|
||||
for line in $header_lines
|
||||
printf '%s%s\n' $header_prefix $line
|
||||
end
|
||||
end
|
||||
|
||||
function merge_po_files --argument-names template_file po_file
|
||||
msgmerge --no-wrap --update --no-fuzzy-matching --backup=none --quiet \
|
||||
$po_file $template_file
|
||||
or cleanup_exit
|
||||
set -l new_po_file (mktemp) # TODO Remove on failure.
|
||||
# Remove obsolete messages instead of keeping them as #~ entries.
|
||||
and msgattrib --no-wrap --no-obsolete -o $new_po_file $po_file
|
||||
or cleanup_exit
|
||||
|
||||
begin
|
||||
print_header
|
||||
# Paste PO file without old header lines.
|
||||
sed '/^'$header_prefix'/d' $new_po_file
|
||||
end >$po_file
|
||||
rm $new_po_file
|
||||
end
|
||||
|
||||
for po_file in $po_files
|
||||
if set --query tmpdir[1]
|
||||
set po_file $tmpdir/(basename $po_file)
|
||||
end
|
||||
if test -e $po_file
|
||||
merge_po_files $template_file $po_file
|
||||
else
|
||||
begin
|
||||
print_header
|
||||
cat $template_file
|
||||
end >$po_file
|
||||
end
|
||||
end
|
||||
|
||||
if set -g --query tmpdir[1]
|
||||
diff -ur $po_dir $tmpdir
|
||||
or begin
|
||||
echo ERROR: translations in localization/po/ are stale. Try running build_tools/update_translations.fish
|
||||
cleanup_exit
|
||||
end
|
||||
end
|
||||
|
||||
cleanup_exit
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -11,6 +11,6 @@ codename=$(
|
||||
curl -fsS https://sources.debian.org/api/src/"${package}"/ |
|
||||
jq -r --arg codename "${codename}" '
|
||||
.versions[] | select(.suites[] == $codename) | .version' |
|
||||
sed 's/^\([0-9]\+\.[0-9]\+\).*/\1/' |
|
||||
sed -E 's/^([0-9]+\.[0-9]+).*/\1/' |
|
||||
sort --version-sort |
|
||||
tail -1
|
||||
|
||||
1
clippy.toml
Normal file
1
clippy.toml
Normal file
@@ -0,0 +1 @@
|
||||
allow-print-in-tests = true
|
||||
@@ -104,20 +104,12 @@ fish_create_dirs(${sysconfdir}/fish/conf.d ${sysconfdir}/fish/completions
|
||||
install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/)
|
||||
|
||||
fish_create_dirs(
|
||||
${rel_datadir}/fish ${rel_datadir}/fish/completions
|
||||
${rel_datadir}/fish/functions
|
||||
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
|
||||
${rel_datadir}/fish/tools/web_config
|
||||
${rel_datadir}/fish/tools/web_config/js
|
||||
${rel_datadir}/fish/prompts
|
||||
${rel_datadir}/fish/themes
|
||||
${rel_datadir}/fish
|
||||
${rel_datadir}/fish/man/man1
|
||||
)
|
||||
|
||||
# This file is embedded in the executable by rust-embed and never read from the filesystem
|
||||
configure_file(share/__fish_build_paths.fish.in share/__fish_build_paths.fish)
|
||||
install(FILES share/config.fish
|
||||
${CMAKE_CURRENT_BINARY_DIR}/share/__fish_build_paths.fish
|
||||
DESTINATION ${rel_datadir}/fish
|
||||
)
|
||||
|
||||
# Create only the vendor directories inside the prefix (#5029 / #6508)
|
||||
fish_create_dirs(
|
||||
@@ -145,30 +137,6 @@ install(
|
||||
DESTINATION ${rel_datadir}/pkgconfig
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY share/completions/
|
||||
DESTINATION ${rel_datadir}/fish/completions
|
||||
FILES_MATCHING PATTERN "*.fish"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY share/functions/
|
||||
DESTINATION ${rel_datadir}/fish/functions
|
||||
FILES_MATCHING PATTERN "*.fish"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY share/prompts/
|
||||
DESTINATION ${rel_datadir}/fish/prompts
|
||||
FILES_MATCHING PATTERN "*.fish"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY share/themes/
|
||||
DESTINATION ${rel_datadir}/fish/themes
|
||||
FILES_MATCHING PATTERN "*.theme"
|
||||
)
|
||||
|
||||
# CONDEMNED_PAGE is managed by the conditional above
|
||||
# Building the man pages is optional: if sphinx isn't installed, they're not built
|
||||
install(
|
||||
@@ -179,22 +147,6 @@ install(
|
||||
PATTERN ${CONDEMNED_PAGE} EXCLUDE
|
||||
)
|
||||
|
||||
install(
|
||||
PROGRAMS share/tools/create_manpage_completions.py
|
||||
DESTINATION ${rel_datadir}/fish/tools/
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY share/tools/web_config
|
||||
DESTINATION ${rel_datadir}/fish/tools/
|
||||
FILES_MATCHING
|
||||
PATTERN "*.png"
|
||||
PATTERN "*.css"
|
||||
PATTERN "*.html"
|
||||
PATTERN "*.py"
|
||||
PATTERN "*.js"
|
||||
)
|
||||
|
||||
# Building the man pages is optional: if Sphinx isn't installed, they're not built
|
||||
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
|
||||
install(
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
fish (4.7.1-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.7.1.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.1 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Fri, 08 May 2026 00:02:14 +0800
|
||||
|
||||
fish (4.7.0-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.7.0.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.0 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 05 May 2026 15:24:27 +0800
|
||||
|
||||
fish (4.6.0-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.6.0.
|
||||
|
||||
@@ -10,6 +10,8 @@ Build-Depends: debhelper-compat (= 13),
|
||||
gettext,
|
||||
libpcre2-dev,
|
||||
rustc (>= 1.85) | rustc-web (>= 1.85) | rustc-1.85,
|
||||
# pkg-config is needed for the pcre2 crate to find the pcre2 system library
|
||||
pkgconf | pkg-config,
|
||||
python3-sphinx,
|
||||
# Test dependencies
|
||||
locales-all,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-build-helper"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -98,14 +98,14 @@ pub fn target_os_is_cygwin() -> bool {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! as_os_strs {
|
||||
[ $( $x:expr, )* ] => {
|
||||
[ $( $x:expr ),* $(,)? ] => {
|
||||
{
|
||||
use std::ffi::OsStr;
|
||||
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
|
||||
s.as_ref()
|
||||
}
|
||||
&[
|
||||
$( as_os_str($x), )*
|
||||
[
|
||||
$( as_os_str($x) ),*
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-build-man-pages"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-color"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-common"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
fd::{AsRawFd, BorrowedFd, RawFd},
|
||||
unix::ffi::OsStrExt as _,
|
||||
},
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc, OnceLock,
|
||||
Arc, LazyLock,
|
||||
atomic::{AtomicI32, AtomicU32, Ordering},
|
||||
},
|
||||
time,
|
||||
@@ -398,7 +399,7 @@ fn escape_string_url(input: &wstr) -> WString {
|
||||
for byte in narrow {
|
||||
if (byte & 0x80) == 0 {
|
||||
let c = char::from_u32(u32::from(byte)).unwrap();
|
||||
if c.is_alphanumeric() || [b'/', b'.', b'~', b'-', b'_'].contains(&byte) {
|
||||
if c.is_alphanumeric() || b"/.~-_".contains(&byte) {
|
||||
// The above characters don't need to be encoded.
|
||||
out.push(c);
|
||||
continue;
|
||||
@@ -534,8 +535,8 @@ enum Mode {
|
||||
let mut to_append_or_none = Some(c);
|
||||
if mode == Mode::Unquoted {
|
||||
match c {
|
||||
'\\' => {
|
||||
if !ignore_backslashes {
|
||||
'\\'
|
||||
if !ignore_backslashes => {
|
||||
// Backslashes (escapes) are complicated and may result in errors, or
|
||||
// appending INTERNAL_SEPARATORs, so we have to handle them specially.
|
||||
if let Some(escape_chars) = read_unquoted_escape(
|
||||
@@ -555,28 +556,25 @@ enum Mode {
|
||||
// We've already appended, don't append anything else.
|
||||
to_append_or_none = None;
|
||||
}
|
||||
}
|
||||
'~' => {
|
||||
'~'
|
||||
if unescape_special
|
||||
&& (input_position == 0 || Some(input_position) == potential_word_start)
|
||||
{
|
||||
=> {
|
||||
to_append_or_none = Some(HOME_DIRECTORY);
|
||||
}
|
||||
}
|
||||
'%' => {
|
||||
'%'
|
||||
// Note that this only recognizes %self if the string is literally %self.
|
||||
// %self/foo will NOT match this.
|
||||
if allow_percent_self
|
||||
&& unescape_special
|
||||
&& input_position == 0
|
||||
&& input == PROCESS_EXPAND_SELF_STR
|
||||
{
|
||||
=> {
|
||||
to_append_or_none = Some(PROCESS_EXPAND_SELF);
|
||||
input_position += PROCESS_EXPAND_SELF_STR.len() - 1; // skip over 'self's
|
||||
}
|
||||
}
|
||||
'*' => {
|
||||
if unescape_special {
|
||||
'*'
|
||||
if unescape_special => {
|
||||
// In general, this is ANY_STRING. But as a hack, if the last appended char
|
||||
// is ANY_STRING, delete the last char and store ANY_STRING_RECURSIVE to
|
||||
// reflect the fact that ** is the recursive wildcard.
|
||||
@@ -588,14 +586,12 @@ enum Mode {
|
||||
to_append_or_none = Some(ANY_STRING);
|
||||
}
|
||||
}
|
||||
}
|
||||
'?' => {
|
||||
if unescape_special && !feature_test(FeatureFlag::QuestionMarkNoGlob) {
|
||||
'?'
|
||||
if unescape_special && !feature_test(FeatureFlag::QuestionMarkNoGlob) => {
|
||||
to_append_or_none = Some(ANY_CHAR);
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
if unescape_special {
|
||||
'$'
|
||||
if unescape_special => {
|
||||
let is_cmdsub = input_position + 1 < input.len()
|
||||
&& input.char_at(input_position + 1) == '(';
|
||||
if !is_cmdsub {
|
||||
@@ -603,18 +599,16 @@ enum Mode {
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
}
|
||||
}
|
||||
'{' => {
|
||||
if unescape_special {
|
||||
'{'
|
||||
if unescape_special => {
|
||||
brace_count += 1;
|
||||
to_append_or_none = Some(BRACE_BEGIN);
|
||||
// We need to store where the brace *ends up* in the output.
|
||||
braces.push(result.len());
|
||||
potential_word_start = Some(input_position + 1);
|
||||
}
|
||||
}
|
||||
'}' => {
|
||||
if unescape_special {
|
||||
'}'
|
||||
if unescape_special => {
|
||||
// HACK: The completion machinery sometimes hands us partial tokens.
|
||||
// We can't parse them properly, but it shouldn't hurt,
|
||||
// so we don't assert here.
|
||||
@@ -646,19 +640,16 @@ enum Mode {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
',' => {
|
||||
if unescape_special && brace_count > 0 {
|
||||
','
|
||||
if unescape_special && brace_count > 0 => {
|
||||
to_append_or_none = Some(BRACE_SEP);
|
||||
vars_or_seps.push(input_position);
|
||||
potential_word_start = Some(input_position + 1);
|
||||
}
|
||||
}
|
||||
' ' => {
|
||||
if unescape_special && brace_count > 0 {
|
||||
' '
|
||||
if unescape_special && brace_count > 0 => {
|
||||
to_append_or_none = Some(BRACE_SPACE);
|
||||
}
|
||||
}
|
||||
'\'' => {
|
||||
mode = Mode::SingleQuotes;
|
||||
to_append_or_none = if unescape_special {
|
||||
@@ -743,11 +734,9 @@ enum Mode {
|
||||
}
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
if unescape_special {
|
||||
to_append_or_none = Some(VARIABLE_EXPAND_SINGLE);
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
'$' if unescape_special => {
|
||||
to_append_or_none = Some(VARIABLE_EXPAND_SINGLE);
|
||||
vars_or_seps.push(input_position);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@@ -1030,9 +1019,8 @@ pub fn read_unquoted_escape(
|
||||
/// session. We err on the side of assuming it's not a console session. This approach isn't
|
||||
/// bullet-proof and that's OK.
|
||||
pub fn is_console_session() -> bool {
|
||||
static IS_CONSOLE_SESSION: OnceLock<bool> = OnceLock::new();
|
||||
// TODO(terminal-workaround)
|
||||
*IS_CONSOLE_SESSION.get_or_init(|| {
|
||||
static IS_CONSOLE_SESSION: LazyLock<bool> = LazyLock::new(||
|
||||
// No console session on Apple, and ttyname may hang (#12506).
|
||||
!cfg!(apple)
|
||||
&& nix::unistd::ttyname(unsafe { std::os::fd::BorrowedFd::borrow_raw(STDIN_FILENO) })
|
||||
@@ -1049,8 +1037,8 @@ pub fn is_console_session() -> bool {
|
||||
// and that $TERM is simple, e.g. `xterm` or `vt100`, not `xterm-something` or `sun-color`.
|
||||
is_console_tty
|
||||
&& env::var_os("TERM").is_none_or(|t| !t.as_bytes().contains(&b'-'))
|
||||
})
|
||||
})
|
||||
}));
|
||||
*IS_CONSOLE_SESSION
|
||||
}
|
||||
|
||||
/// Exits without invoking destructors (via _exit), useful for code after fork.
|
||||
@@ -1197,10 +1185,10 @@ pub fn restore_term_foreground_process_group_for_exit() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around Cell which supports modifying the contents, scoped to a region of code.
|
||||
/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the value,
|
||||
/// instead of requiring an accessor function which returns a mutable reference to a field.
|
||||
pub struct ScopedCell<T>(Cell<T>);
|
||||
/// A wrapper around `Rc<Cell>` which supports modifying the contents, scoped to a region of code.
|
||||
/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the
|
||||
/// value, instead of requiring an accessor function which returns a mutable reference to a field.
|
||||
pub struct ScopedCell<T>(Rc<Cell<T>>);
|
||||
|
||||
impl<T> Deref for ScopedCell<T> {
|
||||
type Target = Cell<T>;
|
||||
@@ -1210,15 +1198,9 @@ fn deref(&self) -> &Self::Target {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> ScopedCell<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(Cell::new(value))
|
||||
Self(Rc::new(Cell::new(value)))
|
||||
}
|
||||
|
||||
/// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped.
|
||||
@@ -1242,19 +1224,22 @@ pub fn new(value: T) -> Self {
|
||||
/// // Restored after scope
|
||||
/// assert_eq!(cell.get(), 5);
|
||||
/// ```
|
||||
pub fn scoped_mod<'a, Modifier: FnOnce(&mut T)>(
|
||||
&'a self,
|
||||
pub fn scoped_mod<Modifier: FnOnce(&mut T)>(
|
||||
&self,
|
||||
modifier: Modifier,
|
||||
) -> impl ScopeGuarding + 'a {
|
||||
) -> impl DerefMut + use<T, Modifier> {
|
||||
let mut val = self.get();
|
||||
modifier(&mut val);
|
||||
let saved = self.replace(val);
|
||||
ScopeGuard::new(self, move |cell| cell.set(saved))
|
||||
let inner = Rc::clone(&self.0);
|
||||
ScopeGuard::new((), move |()| inner.set(saved))
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around RefCell which supports modifying the contents, scoped to a region of code.
|
||||
pub struct ScopedRefCell<T>(RefCell<T>);
|
||||
/// A wrapper around `Rc<RefCell>` which supports modifying the contents, scoped to a region
|
||||
/// of code.
|
||||
#[derive(Default)]
|
||||
pub struct ScopedRefCell<T>(Rc<RefCell<T>>);
|
||||
|
||||
impl<T> Deref for ScopedRefCell<T> {
|
||||
type Target = RefCell<T>;
|
||||
@@ -1264,15 +1249,9 @@ fn deref(&self) -> &Self::Target {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ScopedRefCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ScopedRefCell<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(RefCell::new(value))
|
||||
Self(Rc::new(RefCell::new(value)))
|
||||
}
|
||||
|
||||
/// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped.
|
||||
@@ -1299,19 +1278,20 @@ pub fn new(value: T) -> Self {
|
||||
/// // Restored after scope
|
||||
/// assert_eq!(cell.borrow().flag, false);
|
||||
/// ```
|
||||
pub fn scoped_set<'a, Accessor, Value: 'a>(
|
||||
&'a self,
|
||||
pub fn scoped_set<Accessor, Value>(
|
||||
&self,
|
||||
value: Value,
|
||||
accessor: Accessor,
|
||||
) -> impl ScopeGuarding + 'a
|
||||
) -> impl DerefMut + use<T, Accessor, Value>
|
||||
where
|
||||
Accessor: Fn(&mut T) -> &mut Value + 'a,
|
||||
Accessor: Fn(&mut T) -> &mut Value,
|
||||
{
|
||||
let mut data = self.borrow_mut();
|
||||
let mut saved = std::mem::replace(accessor(&mut data), value);
|
||||
ScopeGuard::new(self, move |cell| {
|
||||
let mut data = cell.borrow_mut();
|
||||
std::mem::swap((accessor)(&mut data), &mut saved);
|
||||
let inner = Rc::clone(&self.0);
|
||||
ScopeGuard::new((), move |()| {
|
||||
let mut inner = inner.borrow_mut();
|
||||
std::mem::swap((accessor)(&mut inner), &mut saved);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1333,7 +1313,7 @@ pub fn scoped_set<'a, Accessor, Value: 'a>(
|
||||
///
|
||||
/// assert_eq!(*cell.borrow(), 10);
|
||||
/// ```
|
||||
pub fn scoped_replace<'a>(&'a self, value: T) -> impl ScopeGuarding + 'a {
|
||||
pub fn scoped_replace(&self, value: T) -> impl DerefMut + use<T> {
|
||||
self.scoped_set(value, |s| s)
|
||||
}
|
||||
}
|
||||
@@ -1372,17 +1352,6 @@ impl<T, F: FnOnce(T)> ScopeGuard<T, F> {
|
||||
pub fn new(value: T, on_drop: F) -> Self {
|
||||
Self(Some((value, on_drop)))
|
||||
}
|
||||
|
||||
/// Invokes the callback, consuming the ScopeGuard.
|
||||
pub fn commit(guard: Self) {
|
||||
std::mem::drop(guard);
|
||||
}
|
||||
|
||||
/// Cancels the invocation of the callback, returning the original wrapped value.
|
||||
pub fn cancel(mut guard: Self) -> T {
|
||||
let (value, _) = guard.0.take().expect("Should always have Some value");
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnOnce(T)> Deref for ScopeGuard<T, F> {
|
||||
@@ -1407,18 +1376,6 @@ fn drop(&mut self) {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait expressing what ScopeGuard can do. This is necessary because our scoped cells return an
|
||||
/// `impl Trait` object and therefore methods on ScopeGuard which take a self parameter cannot be
|
||||
/// used.
|
||||
pub trait ScopeGuarding: DerefMut + Sized {
|
||||
/// Invokes the callback, consuming the guard.
|
||||
fn commit(guard: Self) {
|
||||
std::mem::drop(guard);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnOnce(T)> ScopeGuarding for ScopeGuard<T, F> {}
|
||||
|
||||
pub const fn assert_send<T: Send>() {}
|
||||
pub const fn assert_sync<T: Sync>() {}
|
||||
|
||||
@@ -1574,17 +1531,17 @@ fn test_scope_guard() {
|
||||
counter.fetch_add(1, relaxed);
|
||||
});
|
||||
assert_eq!(counter.load(relaxed), 0);
|
||||
std::mem::drop(guard);
|
||||
drop(guard);
|
||||
assert_eq!(counter.load(relaxed), 1);
|
||||
}
|
||||
// commit also invokes the callback.
|
||||
// The callback is invoked on drop
|
||||
{
|
||||
let guard = ScopeGuard::new(123, |arg| {
|
||||
assert_eq!(arg, 123);
|
||||
counter.fetch_add(1, relaxed);
|
||||
});
|
||||
assert_eq!(counter.load(relaxed), 1);
|
||||
ScopeGuard::commit(guard);
|
||||
drop(guard);
|
||||
assert_eq!(counter.load(relaxed), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-fallback"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-feature-flags"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "fish-gettext-extraction"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
description = "proc-macro for extracting strings for gettext translation"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
description = "proc-macro for extracting strings for gettext translation"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-gettext-maps"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ fn to_raw_str(s: &str) -> String {
|
||||
write!(
|
||||
&mut cached_map_file,
|
||||
"static {}: phf::Map<&'static str, &'static str> = {}",
|
||||
&map_name,
|
||||
map_name,
|
||||
single_language_localization_map.build()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-gettext-mo-file-parser"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-gettext"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "fish-printf"
|
||||
version = "0.2.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.2.1"
|
||||
repository.workspace = true
|
||||
description = "printf implementation, based on musl"
|
||||
repository.workspace = true
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "fish-tempfile"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
nix = { workspace = true, features = ["fs", "feature"] }
|
||||
nix = { workspace = true, features = ["feature", "fs"] }
|
||||
rand.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-util"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -63,14 +63,23 @@ pub fn wcsfilecmp(a: &wstr, b: &wstr) -> Ordering {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sort dashes after Z - see #5634
|
||||
let mut acl = if ac == '-' { '[' } else { ac };
|
||||
let mut bcl = if bc == '-' { '[' } else { bc };
|
||||
let transform = |c| {
|
||||
// Sort dashes after Z - see #5634
|
||||
if c == '-' {
|
||||
return '[';
|
||||
}
|
||||
if c == '/' {
|
||||
return '\0';
|
||||
}
|
||||
c
|
||||
};
|
||||
let ac = transform(ac);
|
||||
let bc = transform(bc);
|
||||
// TODO Compare the tail (enabled by Rust's Unicode support).
|
||||
acl = acl.to_uppercase().next().unwrap();
|
||||
bcl = bcl.to_uppercase().next().unwrap();
|
||||
let ac = ac.to_uppercase().next().unwrap();
|
||||
let bc = bc.to_uppercase().next().unwrap();
|
||||
|
||||
match acl.cmp(&bcl) {
|
||||
match ac.cmp(&bc) {
|
||||
Ordering::Equal => {
|
||||
ai += 1;
|
||||
bi += 1;
|
||||
@@ -133,9 +142,9 @@ pub fn wcsfilecmp_glob(a: &wstr, b: &wstr) -> Ordering {
|
||||
}
|
||||
|
||||
// TODO Compare the tail (enabled by Rust's Unicode support).
|
||||
let acl = ac.to_lowercase().next().unwrap();
|
||||
let bcl = bc.to_lowercase().next().unwrap();
|
||||
match acl.cmp(&bcl) {
|
||||
let ac = ac.to_lowercase().next().unwrap();
|
||||
let bc = bc.to_lowercase().next().unwrap();
|
||||
match ac.cmp(&bc) {
|
||||
Ordering::Equal => {
|
||||
ai += 1;
|
||||
bi += 1;
|
||||
@@ -314,5 +323,8 @@ macro_rules! validate {
|
||||
validate!("a00b", "a0b", Ordering::Less);
|
||||
validate!("a0b", "a00b", Ordering::Greater);
|
||||
validate!("a-b", "azb", Ordering::Greater);
|
||||
validate!("a", "a b", Ordering::Less);
|
||||
validate!("a/", "a b/", Ordering::Less);
|
||||
validate!("a/b", "a b", Ordering::Less); // Note this is arbitrary.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-wcstringutil"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-wgetopt"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-widecharwidth"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license = "CC0-1.0"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "fish-widestring"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
[package]
|
||||
name = "xtask"
|
||||
version = "0.0.0"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anstyle.workspace = true
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
clap_complete.workspace = true
|
||||
fish-build-helper.workspace = true
|
||||
fish-common.workspace = true
|
||||
fish-tempfile.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
ignore.workspace = true
|
||||
pcre2.workspace = true
|
||||
walkdir.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anstyle::{AnsiColor, Style};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use clap::Args;
|
||||
use std::{
|
||||
io::{ErrorKind, Write},
|
||||
@@ -25,12 +26,12 @@ pub struct FormatArgs {
|
||||
paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
pub fn format(args: FormatArgs) {
|
||||
pub fn format(args: FormatArgs) -> Result<()> {
|
||||
if !args.all && args.paths.is_empty() {
|
||||
println!(
|
||||
"{YELLOW}warning: No paths specified. Nothing to do. Use the \"--all\" flag to consider all eligible files.{YELLOW:#}"
|
||||
);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
if !args.force && !args.check {
|
||||
match Command::new("git")
|
||||
@@ -39,16 +40,22 @@ pub fn format(args: FormatArgs) {
|
||||
{
|
||||
Ok(output) => {
|
||||
if !output.stdout.is_empty() {
|
||||
std::io::stdout().write_all(&output.stdout).unwrap();
|
||||
std::io::stdout()
|
||||
.write_all(&output.stdout)
|
||||
.context("Could not write to stdout.")?;
|
||||
print!(
|
||||
"You have uncommitted changes (listed above). Are you sure you want to format? (y/N): "
|
||||
);
|
||||
std::io::stdout().flush().unwrap();
|
||||
std::io::stdout()
|
||||
.flush()
|
||||
.context("Could not flush stdout.")?;
|
||||
let mut response = String::new();
|
||||
std::io::stdin().read_line(&mut response).unwrap();
|
||||
std::io::stdin()
|
||||
.read_line(&mut response)
|
||||
.context("Could not read from stdin.")?;
|
||||
if response.trim_end() != "y" {
|
||||
println!("Exiting without formatting.");
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,22 +65,25 @@ pub fn format(args: FormatArgs) {
|
||||
"{YELLOW}warning: Did not find git, will proceed without checking for unstaged changes.{YELLOW:#}"
|
||||
)
|
||||
} else {
|
||||
fail!("Failed to run git status:\n{e}")
|
||||
bail!("Failed to run git status:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
format_fish(&args);
|
||||
format_python(&args);
|
||||
format_rust(&args);
|
||||
format_fish(&args)?;
|
||||
format_python(&args)?;
|
||||
format_rust(&args)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_formatter(formatter: &mut Command, name: &str) {
|
||||
fn run_formatter(formatter: &mut Command, name: &str) -> Result<()> {
|
||||
println!("=== Running {GREEN}{name}{GREEN:#}");
|
||||
match formatter.status() {
|
||||
Ok(exit_status) => {
|
||||
if !exit_status.success() {
|
||||
fail!("{name:?}: Files are not formatted correctly.");
|
||||
if exit_status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("{name:?}: Files are not formatted correctly.");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -81,15 +91,16 @@ fn run_formatter(formatter: &mut Command, name: &str) {
|
||||
eprintln!(
|
||||
"{YELLOW}Formatter not found: {name:?}. Skipping associated files.{YELLOW:#}"
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
fail!("Error occurred while running {name:?}:\n{e}")
|
||||
Err(e).with_context(|| format!("Error occurred while running {name:?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_fish(args: &FormatArgs) {
|
||||
let mut fish_paths = files_with_extension(&args.paths, "fish");
|
||||
fn format_fish(args: &FormatArgs) -> Result<()> {
|
||||
let mut fish_paths = files_with_extension(&args.paths, "fish")?;
|
||||
if args.all {
|
||||
let workspace_root = fish_build_helper::workspace_root();
|
||||
let fish_formatting_dirs = ["benchmarks", "build_tools", "etc", "share"];
|
||||
@@ -98,10 +109,10 @@ fn format_fish(args: &FormatArgs) {
|
||||
.iter()
|
||||
.map(|dir_name| workspace_root.join(dir_name)),
|
||||
"fish",
|
||||
));
|
||||
)?);
|
||||
};
|
||||
if fish_paths.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
// TODO: make `fish_indent` available as a Rust library function, to avoid needing a
|
||||
// `fish_indent` binary in `$PATH`.
|
||||
@@ -113,40 +124,40 @@ fn format_fish(args: &FormatArgs) {
|
||||
}
|
||||
formatter.arg("--");
|
||||
formatter.args(fish_paths);
|
||||
run_formatter(&mut formatter, "fish_indent");
|
||||
run_formatter(&mut formatter, "fish_indent")
|
||||
}
|
||||
|
||||
fn format_python(args: &FormatArgs) {
|
||||
fn format_python(args: &FormatArgs) -> Result<()> {
|
||||
let mut formatter = Command::new("ruff");
|
||||
formatter.arg("format");
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
}
|
||||
let mut python_files = files_with_extension(&args.paths, "py");
|
||||
let mut python_files = files_with_extension(&args.paths, "py")?;
|
||||
|
||||
if args.all {
|
||||
python_files.push(fish_build_helper::workspace_root().to_owned());
|
||||
};
|
||||
if python_files.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
formatter.args(python_files);
|
||||
run_formatter(&mut formatter, "ruff format");
|
||||
run_formatter(&mut formatter, "ruff format")
|
||||
}
|
||||
|
||||
fn format_rust(args: &FormatArgs) {
|
||||
fn format_rust(args: &FormatArgs) -> Result<()> {
|
||||
let rustfmt_status = Command::new("cargo")
|
||||
.arg("fmt")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.status()
|
||||
.unwrap();
|
||||
.context("Failed to run cargo")?;
|
||||
if !rustfmt_status.success() {
|
||||
eprintln!(
|
||||
"{YELLOW}Please install \"rustfmt\" to format Rust, e.g. via:\n\
|
||||
rustup component add rustfmt{YELLOW:#}"
|
||||
);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
if args.all {
|
||||
let mut formatter = Command::new("cargo");
|
||||
@@ -155,16 +166,17 @@ fn format_rust(args: &FormatArgs) {
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
}
|
||||
run_formatter(&mut formatter, "cargo fmt");
|
||||
run_formatter(&mut formatter, "cargo fmt")?;
|
||||
}
|
||||
let rust_files = files_with_extension(&args.paths, "rs");
|
||||
if !rust_files.is_empty() {
|
||||
let mut formatter = Command::new("rustfmt");
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
formatter.arg("--files-with-diff");
|
||||
}
|
||||
formatter.args(rust_files);
|
||||
run_formatter(&mut formatter, "rustfmt");
|
||||
let rust_files = files_with_extension(&args.paths, "rs")?;
|
||||
if rust_files.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut formatter = Command::new("rustfmt");
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
formatter.arg("--files-with-diff");
|
||||
}
|
||||
formatter.args(rust_files);
|
||||
run_formatter(&mut formatter, "rustfmt")
|
||||
}
|
||||
|
||||
552
crates/xtask/src/gettext.rs
Normal file
552
crates/xtask/src/gettext.rs
Normal file
@@ -0,0 +1,552 @@
|
||||
use crate::{CARGO, CommandExt, files_with_extension};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use clap::{Args, Subcommand};
|
||||
use fish_build_helper::po_dir;
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{Write as _, stdout},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
thread::spawn,
|
||||
};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct GettextArgs {
|
||||
/// Path to the directory into which the messages from the Rust sources have been extracted.
|
||||
/// If this is not specified, fish will be compiled with the `gettext-extract` feature to
|
||||
/// obtain the messages.
|
||||
#[arg(long)]
|
||||
rust_extraction_dir: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
task: Task,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Task {
|
||||
/// Check whether the PO files are up to date.
|
||||
/// Prints a diff and exits non-zero if they are outdated.
|
||||
/// Considers all our PO files by default, also allows explicitly specifying which files to
|
||||
/// consider.
|
||||
Check { paths: Vec<PathBuf> },
|
||||
/// Add a PO file for a new language.
|
||||
New {
|
||||
/// An ISO 639-1 language identifier (ISO 639-2 if the former does not exits),
|
||||
/// optionally followed by and underscore and an ISO 3166-1 country code to specify the variant,
|
||||
/// e.g. `de` or `pt_BR`.
|
||||
language: String,
|
||||
},
|
||||
/// Update PO files.
|
||||
/// This will delete entries for msgids which are no longer used in the sources and introduce
|
||||
/// new, untranslated entries for messages which do not have an entry yet.
|
||||
/// This tool should run every time a change to the messages localized via gettext occurs,
|
||||
/// including fish script files, where many strings are implicitly localized.
|
||||
/// Considers all our PO files by default, also allows explicitly specifying which files to
|
||||
/// consider.
|
||||
Update { paths: Vec<PathBuf> },
|
||||
}
|
||||
|
||||
fn get_po_paths<P: AsRef<Path>>(specified_paths: &[P]) -> Result<Vec<PathBuf>> {
|
||||
let extension = "po";
|
||||
if specified_paths.is_empty() {
|
||||
files_with_extension([po_dir()], extension)
|
||||
} else {
|
||||
files_with_extension(specified_paths, extension)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_po_file<P: AsRef<Path>, Q: AsRef<Path>>(file_to_update: P, template: Q) -> Result<()> {
|
||||
Command::new("msgmerge")
|
||||
.args([
|
||||
"--no-wrap",
|
||||
"--update",
|
||||
"--no-fuzzy-matching",
|
||||
"--backup=none",
|
||||
"--quiet",
|
||||
])
|
||||
.arg(file_to_update.as_ref())
|
||||
.arg(template.as_ref())
|
||||
.run()?;
|
||||
let msgattrib_output_file = fish_tempfile::new_file().context("Failed to create temp file")?;
|
||||
Command::new("msgattrib")
|
||||
.args(["--no-wrap", "--no-obsolete"])
|
||||
.arg("-o")
|
||||
.arg(msgattrib_output_file.path())
|
||||
.arg(file_to_update.as_ref())
|
||||
.run()?;
|
||||
crate::copy_file(msgattrib_output_file.path(), file_to_update.as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gettext(args: GettextArgs) -> Result<()> {
|
||||
let template = match args.rust_extraction_dir {
|
||||
Some(dir) => template::Template::new(dir)?,
|
||||
None => {
|
||||
let temp_dir = fish_tempfile::new_dir().context("Failed to create temp file")?;
|
||||
Command::new(CARGO)
|
||||
.args([
|
||||
"check",
|
||||
"--workspace",
|
||||
"--all-targets",
|
||||
"--features=gettext-extract",
|
||||
])
|
||||
.env("FISH_GETTEXT_EXTRACTION_DIR", temp_dir.path())
|
||||
.run()?;
|
||||
template::Template::new(temp_dir.path())?
|
||||
}
|
||||
};
|
||||
let mut template_file = fish_tempfile::new_file().context("Failed to create temp file")?;
|
||||
template_file
|
||||
.get_mut()
|
||||
.write_all(template.serialize())
|
||||
.with_context(|| format!("Failed to write to temp file {:?}", template_file.path()))?;
|
||||
template_file
|
||||
.get_mut()
|
||||
.flush()
|
||||
.with_context(|| format!("Failed to flush temporary file {:?}", template_file.path()))?;
|
||||
|
||||
match args.task {
|
||||
Task::Check { paths } => {
|
||||
let mut thread_handles = vec![];
|
||||
for path in get_po_paths(&paths)? {
|
||||
let template_path_buf = template_file.path().to_owned();
|
||||
let handle = spawn(move || -> Result<Option<Vec<u8>>> {
|
||||
let tmp_copy =
|
||||
fish_tempfile::new_file().context("Failed to create temp file")?;
|
||||
crate::copy_file(&path, tmp_copy.path())?;
|
||||
update_po_file(tmp_copy.path(), template_path_buf)?;
|
||||
let diff_output = Command::new("diff")
|
||||
.arg("-u")
|
||||
.arg(&path)
|
||||
.arg(tmp_copy.path())
|
||||
.output()
|
||||
.context("Failed to run diff")?;
|
||||
if diff_output.status.success() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(diff_output.stdout))
|
||||
}
|
||||
});
|
||||
thread_handles.push(handle);
|
||||
}
|
||||
let mut found_diff = false;
|
||||
let mut error = None;
|
||||
for handle in thread_handles {
|
||||
// SAFETY: `handle.join()` only returns `Err` if the thread panicked.
|
||||
// Our threads should not panic, and if they do, it's OK to deal with the unexpected
|
||||
// behavior by panicking here as well.
|
||||
match handle.join().unwrap() {
|
||||
Ok(None) => {}
|
||||
Ok(Some(diff)) => {
|
||||
found_diff = true;
|
||||
stdout()
|
||||
.write_all(&diff)
|
||||
.context("Could not write to stdout")?;
|
||||
}
|
||||
Err(e) => {
|
||||
error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(e) = error {
|
||||
return Err(e);
|
||||
}
|
||||
if found_diff {
|
||||
bail!(
|
||||
"Not all PO files are up to date.\n\
|
||||
Run `cargo xtask gettext update` to bring them up to date automatically.\
|
||||
"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Task::New { language } => {
|
||||
let language_regex = Regex::new("^[a-z]{2,3}(_[A-Z]{2})?$").unwrap();
|
||||
if !language_regex.is_match(language.as_bytes()).unwrap() {
|
||||
bail!(
|
||||
"The language name '{language}' does not match the expected format.\n\
|
||||
It needs to be a two-letter ISO 639-1 language code, \
|
||||
or a three-letter ISO 639-2 language code \
|
||||
if no ISO 639-1 code exists for the language.\n\
|
||||
Optionally, the language code can be followed be an underscore \
|
||||
followed by an ISO 3166-1 country code to indicate a regional variant.\n\
|
||||
Check the existing file names in {:?} for examples.",
|
||||
po_dir()
|
||||
);
|
||||
}
|
||||
// TODO (MSRV>=1.91): use with_added_extension instead of with_extension
|
||||
let po_path = po_dir().join(&language).with_extension("po");
|
||||
let mut new_po_file = OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&po_path)
|
||||
.with_context(|| format!("Failed to create file at {po_path:?}"))?;
|
||||
let mut header = String::new();
|
||||
let line_prefix = "# fish-note-sections: ";
|
||||
let lines = [
|
||||
"Translations are divided into sections, each starting with a fish-section-* pseudo-message.",
|
||||
"The first few sections are more important.",
|
||||
"Ignore the tier3 sections unless you have a lot of time.",
|
||||
];
|
||||
for line in lines {
|
||||
use std::fmt::Write as _;
|
||||
let _ = writeln!(header, "{line_prefix}{line}");
|
||||
}
|
||||
new_po_file
|
||||
.write_all(header.as_bytes())
|
||||
.with_context(|| format!("Failed to write to {po_path:?}"))?;
|
||||
new_po_file
|
||||
.write_all(template.serialize())
|
||||
.with_context(|| format!("Failed to write to {po_path:?}"))?;
|
||||
Ok(())
|
||||
}
|
||||
Task::Update { paths } => {
|
||||
let mut thread_handles = vec![];
|
||||
for path in get_po_paths(&paths)? {
|
||||
let template_path_buf = template_file.path().to_owned();
|
||||
let handle =
|
||||
spawn(move || -> Result<()> { update_po_file(path, template_path_buf) });
|
||||
thread_handles.push(handle);
|
||||
}
|
||||
let mut error = None;
|
||||
for handle in thread_handles {
|
||||
// SAFETY: `handle.join()` only returns `Err` if the thread panicked.
|
||||
// Our threads should not panic, and if they do, it's OK to deal with the unexpected
|
||||
// behavior by panicking here as well.
|
||||
if let Err(e) = handle.join().unwrap() {
|
||||
error = Some(e);
|
||||
}
|
||||
}
|
||||
if let Some(e) = error {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod template {
|
||||
use crate::{CommandExt as _, files_with_extension};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use fish_build_helper::workspace_root;
|
||||
use fish_common::{UnescapeFlags, unescape_string};
|
||||
use fish_widestring::{str2wcstring, wcs2bytes};
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Display,
|
||||
fs::OpenOptions,
|
||||
io::Read as _,
|
||||
path::Path,
|
||||
process::Command,
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
// Gettext tools require this header to know which encoding is used.
|
||||
const MINIMAL_HEADER: &str = r#"msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
"#;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||
enum LocalizationTier {
|
||||
Tier1,
|
||||
Tier2,
|
||||
Tier3,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for LocalizationTier {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"tier1" => Ok(Self::Tier1),
|
||||
"tier2" => Ok(Self::Tier2),
|
||||
"tier3" => Ok(Self::Tier3),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LocalizationTier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Tier1 => "tier1",
|
||||
Self::Tier2 => "tier2",
|
||||
Self::Tier3 => "tier3",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FishScriptMessages {
|
||||
explicit: HashSet<String>,
|
||||
implicit: HashSet<String>,
|
||||
}
|
||||
|
||||
pub struct Template {
|
||||
content: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Template {
|
||||
pub fn serialize(&self) -> &[u8] {
|
||||
&self.content
|
||||
}
|
||||
|
||||
/// Create a gettext template.
|
||||
/// `rust_extraction_dir` must be the path to a directory which contains the messages
|
||||
/// extracted from the Rust sources.
|
||||
pub fn new<P: AsRef<Path>>(rust_extraction_dir: P) -> Result<Self> {
|
||||
let mut template = Self {
|
||||
content: Vec::from(MINIMAL_HEADER.as_bytes()),
|
||||
};
|
||||
template.add_rust_messages(rust_extraction_dir)?;
|
||||
template.add_fish_script_messages()?;
|
||||
// TODO: keep internal set of msgids to avoid having to run msguniq. requires parsing
|
||||
// gettext-extraction output
|
||||
let msguniq_output = Command::new("msguniq")
|
||||
.args(["--no-wrap"])
|
||||
.run_with_stdio(template.content)?;
|
||||
Ok(Template {
|
||||
content: msguniq_output,
|
||||
})
|
||||
}
|
||||
|
||||
/// Expects `extraction_dir` to contain only files whose content are single PO entries which can be
|
||||
/// concatenated into a valid PO file.
|
||||
/// If this is the case, the messages are de-duplicated and sorted by `msguniq`.
|
||||
/// The result is appended to `template`, with a leading section marker.
|
||||
/// On failure, the process aborts.
|
||||
fn add_rust_messages<P: AsRef<Path>>(&mut self, extraction_dir: P) -> Result<()> {
|
||||
let extraction_dir = extraction_dir.as_ref();
|
||||
let mut concatenated_content = Vec::from(MINIMAL_HEADER.as_bytes());
|
||||
|
||||
// Concatenate the content of all files in `extraction_dir` into `concatenated_content`.
|
||||
for entry_result in extraction_dir
|
||||
.read_dir()
|
||||
.with_context(|| format!("Failed to read directory {extraction_dir:?}"))?
|
||||
{
|
||||
let entry = entry_result
|
||||
.with_context(|| format!("Failed to get entry in {extraction_dir:?}"))?;
|
||||
let entry_path = entry.path();
|
||||
if !entry
|
||||
.file_type()
|
||||
.with_context(|| format!("Failed to get file type of {entry_path:?}"))?
|
||||
.is_file()
|
||||
{
|
||||
bail!("Entry in {extraction_dir:?} is not a regular file");
|
||||
}
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&entry_path)
|
||||
.with_context(|| format!("Failed to open file {entry_path:?}"))?;
|
||||
file.read_to_end(&mut concatenated_content)
|
||||
.with_context(|| format!("Failed to read file {entry_path:?}"))?;
|
||||
}
|
||||
|
||||
// Get rid of duplicates and sort.
|
||||
let msguniq_output = Command::new("msguniq")
|
||||
.args(["--no-wrap", "--sort-output"])
|
||||
.env("LC_ALL", "C.UTF-8")
|
||||
.run_with_stdio(concatenated_content)?;
|
||||
// The Header entry needs to be removed again,
|
||||
// because it is added outside of this function.
|
||||
let expected_prefix = MINIMAL_HEADER.as_bytes();
|
||||
let actual_prefix = &msguniq_output[..expected_prefix.len()];
|
||||
if expected_prefix != actual_prefix {
|
||||
bail!(
|
||||
"Prefix of msguniq output does not match expected header.\nExpected bytes:\n{expected_prefix:02x?}\nActual bytes:\n{actual_prefix:02x?}"
|
||||
);
|
||||
}
|
||||
self.mark_section("tier1-from-rust");
|
||||
self.content
|
||||
.extend_from_slice(&msguniq_output[expected_prefix.len()..]);
|
||||
self.content.push(b'\n');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_section(&mut self, section_name: &str) {
|
||||
self.content
|
||||
.extend_from_slice("msgid \"fish-section-".as_bytes());
|
||||
self.content.extend_from_slice(section_name.as_bytes());
|
||||
self.content
|
||||
.extend_from_slice("\"\nmsgstr \"\"\n\n".as_bytes());
|
||||
}
|
||||
|
||||
fn append_messages(&mut self, msgids: &HashSet<String>) -> Result<()> {
|
||||
let mut unescaped_msgids = HashSet::new();
|
||||
for msgid in msgids {
|
||||
let unescaped_wstring = unescape_string(
|
||||
&str2wcstring(msgid),
|
||||
fish_common::UnescapeStringStyle::Script(UnescapeFlags::default()),
|
||||
)
|
||||
.with_context(|| format!("Failed to unescape the following string:\n{msgid}"))?;
|
||||
unescaped_msgids.insert(
|
||||
String::from_utf8(wcs2bytes(&unescaped_wstring))
|
||||
.context("Parsed msgid is not valid UTF-8")?,
|
||||
);
|
||||
}
|
||||
let mut unescaped_msgids = Vec::from_iter(unescaped_msgids);
|
||||
unescaped_msgids.sort();
|
||||
for msgid in &unescaped_msgids {
|
||||
self.content
|
||||
.extend_from_slice(format_msgid_for_po(msgid).as_bytes());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_script_tier(
|
||||
&mut self,
|
||||
tier: LocalizationTier,
|
||||
messages: FishScriptMessages,
|
||||
) -> Result<()> {
|
||||
if !messages.explicit.is_empty() {
|
||||
self.mark_section(&format!("{tier}-from-script-explicitly-added"));
|
||||
self.append_messages(&messages.explicit)?;
|
||||
}
|
||||
if !messages.implicit.is_empty() {
|
||||
self.mark_section(&format!("{tier}-from-script-implicitly-added"));
|
||||
self.append_messages(&messages.implicit)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_fish_script_messages(&mut self) -> Result<()> {
|
||||
let share_dir = workspace_root().join("share");
|
||||
let relevant_file_paths = files_with_extension(
|
||||
[
|
||||
share_dir.join("config.fish"),
|
||||
share_dir.join("completions"),
|
||||
share_dir.join("functions"),
|
||||
],
|
||||
"fish",
|
||||
)?;
|
||||
let mut extracted_messages = HashMap::new();
|
||||
for path in relevant_file_paths {
|
||||
extract_messages_from_fish_script(path, &mut extracted_messages)?;
|
||||
}
|
||||
let mut messages_sorted_by_tier: Vec<_> = extracted_messages.into_iter().collect();
|
||||
messages_sorted_by_tier.sort_by_key(|(tier, _)| *tier);
|
||||
for (tier, messages) in messages_sorted_by_tier {
|
||||
self.add_script_tier(tier, messages)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn find_localization_tier<P: AsRef<Path>>(
|
||||
input: &str,
|
||||
path: P,
|
||||
) -> Result<Option<LocalizationTier>> {
|
||||
static L10N_ANNOTATION: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?:^|\n)# localization: (?<annotation_value>.*)\n").unwrap()
|
||||
});
|
||||
if let Some(annotation) = L10N_ANNOTATION.captures(input.as_bytes()).unwrap() {
|
||||
// SAFETY: `tier` is the name of a capture group in the regex whose captures we are looking
|
||||
// at. The capture is done on the bytes of an UTF-8 encoded string, so the result will also
|
||||
// be UTF-8 encoded, and the sub-slice we are looking at will start and end at codepoint
|
||||
// boundaries.
|
||||
let annotation_value =
|
||||
std::str::from_utf8(annotation.name("annotation_value").unwrap().as_bytes())
|
||||
.unwrap();
|
||||
if let Ok(tier) = LocalizationTier::try_from(annotation_value) {
|
||||
return Ok(Some(tier));
|
||||
}
|
||||
if annotation_value.starts_with("skip") {
|
||||
return Ok(None);
|
||||
}
|
||||
bail!(
|
||||
"Unexpected localization annotation in file {:?}: {annotation_value}",
|
||||
path.as_ref()
|
||||
);
|
||||
}
|
||||
let dirname = path
|
||||
.as_ref()
|
||||
.parent()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Tried to get the parent of a path which does not have a parent: {:?}",
|
||||
path.as_ref()
|
||||
)
|
||||
})?
|
||||
.file_name()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"The parent of {:?} does not have a filename component",
|
||||
path.as_ref()
|
||||
)
|
||||
})?;
|
||||
let command_name = path
|
||||
.as_ref()
|
||||
.file_stem()
|
||||
.with_context(|| format!("The path {:?} does not have a file stem", path.as_ref()))?;
|
||||
if dirname == "functions"
|
||||
&& command_name
|
||||
.to_str()
|
||||
.is_some_and(|name| name.starts_with("fish_"))
|
||||
{
|
||||
return Ok(Some(LocalizationTier::Tier1));
|
||||
}
|
||||
if dirname != "completions" {
|
||||
bail!(
|
||||
"Missing localization tier for function file {:?}",
|
||||
path.as_ref()
|
||||
);
|
||||
}
|
||||
// TODO (MSRV>=1.91): use with_added_extension instead of with_extension
|
||||
let doc_path = workspace_root()
|
||||
.join("doc_src")
|
||||
.join("cmds")
|
||||
.join(command_name)
|
||||
.with_extension("rst");
|
||||
let doc_path_exists = std::fs::exists(&doc_path)
|
||||
.with_context(|| format!("Failed to check whether a file exists at {doc_path:?}"))?;
|
||||
Ok(Some(if doc_path_exists {
|
||||
LocalizationTier::Tier1
|
||||
} else {
|
||||
LocalizationTier::Tier3
|
||||
}))
|
||||
}
|
||||
|
||||
fn extract_messages_from_fish_script<P: AsRef<Path>>(
|
||||
path: P,
|
||||
extracted_messages: &mut HashMap<LocalizationTier, FishScriptMessages>,
|
||||
) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let file_content = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("Failed to read from {path:?}"))?;
|
||||
let Some(tier) = find_localization_tier(&file_content, path)? else {
|
||||
return Ok(());
|
||||
};
|
||||
// TODO: use proper parser instead of regex
|
||||
static EXPLICIT_MESSAGE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"\( *_ (?<message>(['"]).+?(?<!\\)\2) *\)"#).unwrap());
|
||||
static IMPLICIT_MESSAGE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#"(?:^|\n)(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (?<message>(['"]).+?(?<!\\)\2)"#).unwrap()
|
||||
});
|
||||
let messages_at_tier = extracted_messages.entry(tier).or_default();
|
||||
for message in EXPLICIT_MESSAGE.captures_iter(file_content.as_bytes()) {
|
||||
let message =
|
||||
std::str::from_utf8(message.unwrap().name("message").unwrap().as_bytes()).unwrap();
|
||||
messages_at_tier.explicit.insert(message.to_owned());
|
||||
}
|
||||
for message in IMPLICIT_MESSAGE.captures_iter(file_content.as_bytes()) {
|
||||
let message =
|
||||
std::str::from_utf8(message.unwrap().name("message").unwrap().as_bytes()).unwrap();
|
||||
messages_at_tier.implicit.insert(message.to_owned());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_msgid_for_po(msgid: &str) -> String {
|
||||
let escaped_msgid = msgid.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
format!(
|
||||
"\
|
||||
msgid \"{escaped_msgid}\"\n\
|
||||
msgstr \"\"\n\
|
||||
\n\
|
||||
"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,101 @@
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
macro_rules! fail {
|
||||
($($arg:tt)+) => {{
|
||||
eprintln!($($arg)+);
|
||||
std::process::exit(1);
|
||||
}}
|
||||
}
|
||||
|
||||
pub mod format;
|
||||
pub mod gettext;
|
||||
pub mod shellcheck;
|
||||
|
||||
pub trait CommandExt {
|
||||
fn run_or_fail(&mut self);
|
||||
fn run(&mut self) -> Result<()>;
|
||||
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
fn run_or_fail(&mut self) {
|
||||
match self.status() {
|
||||
Ok(exit_status) => {
|
||||
if !exit_status.success() {
|
||||
fail!("Command did not run successfully: {:?}", self.get_program())
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
fail!("Failed to run command: {err}")
|
||||
}
|
||||
fn run(&mut self) -> Result<()> {
|
||||
if !self
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run {:?}", self.get_program()))?
|
||||
.success()
|
||||
{
|
||||
bail!("Command did not run successfully: {:?}", self.get_program())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>> {
|
||||
let command_name = self.get_program().to_owned();
|
||||
let mut child = self
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.with_context(|| format!("Failed to run {command_name:?}"))?;
|
||||
child
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(&stdin)
|
||||
.with_context(|| format!("Failed to write to stdin of {command_name:?}"))?;
|
||||
let command_output = child
|
||||
.wait_with_output()
|
||||
.with_context(|| format!("Failed to read stdout of {command_name:?}"))?;
|
||||
if !command_output.status.success() {
|
||||
bail!("{command_name:?} failed");
|
||||
}
|
||||
Ok(command_output.stdout)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cargo<I, S>(cargo_args: I)
|
||||
pub const CARGO: &str = env!("CARGO");
|
||||
|
||||
pub fn cargo<I, S>(cargo_args: I) -> Result<()>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
Command::new(env!("CARGO")).args(cargo_args).run_or_fail();
|
||||
Command::new(CARGO).args(cargo_args).run()
|
||||
}
|
||||
|
||||
fn get_matching_files<P: AsRef<Path>, I: IntoIterator<Item = P>, M: Fn(&Path) -> bool>(
|
||||
all_paths: I,
|
||||
matcher: M,
|
||||
) -> Vec<PathBuf> {
|
||||
all_paths
|
||||
.into_iter()
|
||||
.flat_map(WalkDir::new)
|
||||
.filter_map(|res| {
|
||||
let entry = res.unwrap();
|
||||
let path = entry.path();
|
||||
if entry.file_type().is_file() && matcher(path) {
|
||||
Some(path.to_owned())
|
||||
} else {
|
||||
None
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let mut matching_files = vec![];
|
||||
for path in all_paths {
|
||||
for dir_entry in WalkDir::new(path.as_ref()) {
|
||||
let dir_entry = dir_entry
|
||||
.with_context(|| format!("Failed to check paths at {:?}", path.as_ref()))?;
|
||||
let path = dir_entry.path();
|
||||
if dir_entry.file_type().is_file() && matcher(path) {
|
||||
matching_files.push(path.to_owned());
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
Ok(matching_files)
|
||||
}
|
||||
|
||||
fn files_with_extension<P: AsRef<Path>, I: IntoIterator<Item = P>>(
|
||||
all_paths: I,
|
||||
extension: &str,
|
||||
) -> Vec<PathBuf> {
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let matcher = |p: &Path| p.extension().is_some_and(|e| e == extension);
|
||||
get_matching_files(all_paths, matcher)
|
||||
}
|
||||
|
||||
fn copy_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
|
||||
std::fs::copy(&from, &to)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to copy from {:?} to {:?}",
|
||||
from.as_ref(),
|
||||
to.as_ref()
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use clap_complete::CompleteEnv;
|
||||
use fish_build_helper::as_os_strs;
|
||||
use std::{path::PathBuf, process::Command};
|
||||
use xtask::{CommandExt as _, cargo, format::FormatArgs};
|
||||
use xtask::{CommandExt, cargo, format::FormatArgs, gettext::GettextArgs, shellcheck::shellcheck};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
@@ -20,6 +22,8 @@ enum Task {
|
||||
Check,
|
||||
/// Format files or check if they are correctly formatted.
|
||||
Format(FormatArgs),
|
||||
/// Work on the gettext PO files.
|
||||
Gettext(GettextArgs),
|
||||
/// Build HTML docs
|
||||
HtmlDocs {
|
||||
/// Path to a fish_indent executable. If none is specified, fish_indent will be built.
|
||||
@@ -28,53 +32,85 @@ enum Task {
|
||||
},
|
||||
/// Build man pages
|
||||
ManPages,
|
||||
/// Run ShellCheck on non-fish shell scripts
|
||||
#[command(name = "shellcheck")]
|
||||
ShellCheck,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
/// Only used to enable completion generation.
|
||||
/// [`clap_complete`] is not built to account for the situation we have here, where the CLI does not
|
||||
/// correspond to a top-level shell command.
|
||||
/// We work around this here by pretending that we are building a CLI for the `cargo` command, which
|
||||
/// only has the single subcommand `xtask`.
|
||||
/// These completions can then be combined with the regular cargo completions.
|
||||
#[derive(Parser)]
|
||||
#[command(name = "cargo")]
|
||||
struct FakeCargoWrapperForCompletion {
|
||||
#[command(subcommand)]
|
||||
xtask: FakeCliForCompletion,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum FakeCliForCompletion {
|
||||
/// Run fish's xtasks
|
||||
#[command(subcommand)]
|
||||
Xtask(Task),
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
CompleteEnv::with_factory(FakeCargoWrapperForCompletion::command).complete();
|
||||
|
||||
let cli = Cli::parse();
|
||||
match cli.task {
|
||||
Task::Check => run_checks(),
|
||||
Task::Format(format_args) => xtask::format::format(format_args),
|
||||
Task::Gettext(gettext_args) => xtask::gettext::gettext(gettext_args),
|
||||
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),
|
||||
Task::ManPages => cargo(["build", "--package", "fish-build-man-pages"]),
|
||||
Task::ShellCheck => shellcheck(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_checks() {
|
||||
fn run_checks() -> Result<()> {
|
||||
let repo_root_dir = fish_build_helper::workspace_root();
|
||||
let check_script = repo_root_dir.join("build_tools").join("check.sh");
|
||||
Command::new(check_script).run_or_fail();
|
||||
Command::new(check_script).run()
|
||||
}
|
||||
|
||||
fn build_html_docs(fish_indent: Option<PathBuf>) {
|
||||
let fish_indent_path = fish_indent.unwrap_or_else(|| {
|
||||
// Build fish_indent if no existing one is specified.
|
||||
cargo([
|
||||
"build",
|
||||
"--bin",
|
||||
"fish_indent",
|
||||
"--profile",
|
||||
"dev",
|
||||
"--no-default-features",
|
||||
]);
|
||||
fish_build_helper::fish_build_dir()
|
||||
.join("debug")
|
||||
.join("fish_indent")
|
||||
});
|
||||
fn build_html_docs(fish_indent: Option<PathBuf>) -> Result<()> {
|
||||
let fish_indent_path = match fish_indent {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
// Build fish_indent if no existing one is specified.
|
||||
cargo([
|
||||
"build",
|
||||
"--bin",
|
||||
"fish_indent",
|
||||
"--profile",
|
||||
"dev",
|
||||
"--no-default-features",
|
||||
])?;
|
||||
fish_build_helper::fish_build_dir()
|
||||
.join("debug")
|
||||
.join("fish_indent")
|
||||
}
|
||||
};
|
||||
// Set path so `sphinx-build` can find `fish_indent`.
|
||||
// Create tempdir to store symlink to fish_indent.
|
||||
// This is done to avoid adding other binaries to the PATH.
|
||||
let tempdir = fish_tempfile::new_dir().unwrap();
|
||||
let tempdir = fish_tempfile::new_dir().context("Failed to create tempdir")?;
|
||||
std::os::unix::fs::symlink(
|
||||
std::fs::canonicalize(fish_indent_path).unwrap(),
|
||||
std::fs::canonicalize(&fish_indent_path).with_context(|| {
|
||||
format!("Failed to canonicalize path to `fish_indent`: {fish_indent_path:?}")
|
||||
})?,
|
||||
tempdir.path().join("fish_indent"),
|
||||
)
|
||||
.unwrap();
|
||||
let new_path = format!(
|
||||
"{}:{}",
|
||||
tempdir.path().to_str().unwrap(),
|
||||
fish_build_helper::env_var("PATH").unwrap()
|
||||
);
|
||||
.context("Failed to create symlink for fish_indent")?;
|
||||
let mut new_path = tempdir.path().as_os_str().to_owned();
|
||||
if let Some(current_path) = std::env::var_os("PATH") {
|
||||
new_path.push(":");
|
||||
new_path.push(current_path);
|
||||
}
|
||||
let doc_src_dir = fish_build_helper::workspace_root().join("doc_src");
|
||||
let doctrees_dir = fish_build_helper::fish_doc_dir().join(".doctrees-html");
|
||||
let html_dir = fish_build_helper::fish_doc_dir().join("html");
|
||||
@@ -94,5 +130,5 @@ fn build_html_docs(fish_indent: Option<PathBuf>) {
|
||||
Command::new(option_env!("FISH_SPHINX").unwrap_or("sphinx-build"))
|
||||
.env("PATH", new_path)
|
||||
.args(args)
|
||||
.run_or_fail();
|
||||
.run()
|
||||
}
|
||||
|
||||
52
crates/xtask/src/shellcheck.rs
Normal file
52
crates/xtask/src/shellcheck.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use anyhow::{Context, Result};
|
||||
use fish_build_helper::workspace_root;
|
||||
use ignore::Walk;
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use crate::CommandExt;
|
||||
|
||||
pub fn shellcheck() -> Result<()> {
|
||||
Command::new("shellcheck")
|
||||
.args(files_to_check()?)
|
||||
.current_dir(workspace_root())
|
||||
.run()
|
||||
}
|
||||
|
||||
fn is_shell_script<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||
let file = File::open(&path).with_context(|| format!("Failed to open {:?}", path.as_ref()))?;
|
||||
let mut first_line = String::new();
|
||||
let Ok(_) = BufReader::new(file).read_line(&mut first_line) else {
|
||||
return Ok(false);
|
||||
};
|
||||
static SHEBANG_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("^#!.*[^i]sh").unwrap());
|
||||
Ok(SHEBANG_REGEX
|
||||
.is_match(first_line.trim().as_bytes())
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn files_to_check() -> Result<Vec<PathBuf>> {
|
||||
let mut files = vec![];
|
||||
for dir_entry in Walk::new(workspace_root()) {
|
||||
let dir_entry = dir_entry.context("Error traversing workspace")?;
|
||||
if !dir_entry
|
||||
.file_type()
|
||||
.with_context(|| format!("Failed to determine file type of {dir_entry:?}"))?
|
||||
.is_file()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let path = dir_entry.into_path();
|
||||
if !is_shell_script(&path)? {
|
||||
continue;
|
||||
}
|
||||
files.push(path);
|
||||
}
|
||||
Ok(files)
|
||||
}
|
||||
34
deny.toml
34
deny.toml
@@ -1,24 +1,24 @@
|
||||
[licenses]
|
||||
# We want really high confidence when inferring licenses from text
|
||||
confidence-threshold = 0.93
|
||||
unused-allowed-license = "allow" # don't warn for unused licenses in this list
|
||||
unused-allowed-license = "allow" # don't warn for unused licenses in this list
|
||||
allow = [
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"GPL-2.0",
|
||||
"GPL-2.0-only",
|
||||
"ISC",
|
||||
"LGPL-2.0",
|
||||
"LGPL-2.0-or-later",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"PSF-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Unicode-3.0",
|
||||
"WTFPL",
|
||||
"Zlib",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"GPL-2.0",
|
||||
"GPL-2.0-only",
|
||||
"ISC",
|
||||
"LGPL-2.0",
|
||||
"LGPL-2.0-or-later",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"PSF-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Unicode-3.0",
|
||||
"WTFPL",
|
||||
"Zlib",
|
||||
]
|
||||
|
||||
[sources.allow-org]
|
||||
|
||||
@@ -50,25 +50,3 @@ Or the simple default handler::
|
||||
function fish_command_not_found
|
||||
__fish_default_command_not_found_handler $argv
|
||||
end
|
||||
|
||||
Backwards compatibility
|
||||
-----------------------
|
||||
|
||||
This command was introduced in fish 3.2.0. Previous versions of fish used the "fish_command_not_found" :ref:`event <event>` instead.
|
||||
|
||||
To define a handler that works in older versions of fish as well, define it the old way::
|
||||
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
echo COMMAND WAS NOT FOUND MY FRIEND $argv[1]
|
||||
end
|
||||
|
||||
in which case fish will define a ``fish_command_not_found`` that calls it,
|
||||
or define a wrapper::
|
||||
|
||||
function fish_command_not_found
|
||||
echo "G'day mate, could not find your command: $argv"
|
||||
end
|
||||
|
||||
function __fish_command_not_found_handler --on-event fish_command_not_found
|
||||
fish_command_not_found $argv
|
||||
end
|
||||
|
||||
@@ -32,11 +32,6 @@ Fish's vi mode aims to be familiar to vim users, but there are some differences:
|
||||
**Word character handling**
|
||||
In vim, underscore (``_``) is treated as a keyword character by default, so word motions like ``w``, ``b``, and ``e`` treat ``foo_bar`` as a single word. In fish, underscore is treated as punctuation, so word motions stop at underscores. For example, pressing ``w`` on ``foo_bar`` in fish stops at the ``_``, while in vim it would jump past the entire identifier.
|
||||
|
||||
**The** ``cw`` **command**
|
||||
In vim, ``cw`` has special behavior: when the cursor is on a non-space character, it behaves like ``ce`` (change to end of word), but when the cursor is on a space, it behaves like ``dwi`` (delete word then insert).
|
||||
|
||||
In fish, ``cw`` always behaves like ``dwi`` - it deletes to the start of the next word (including trailing whitespace), then enters insert mode. To get vim's ``cw`` behavior in fish, use ``ce`` instead.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ The event handler switches (``on-event``, ``on-variable``, ``on-job-exit``, ``on
|
||||
|
||||
Functions names cannot be reserved words. These are elements of fish syntax or builtin commands which are essential for the operations of the shell. Current reserved words are ``[``, ``_``, ``and``, ``argparse``, ``begin``, ``break``, ``builtin``, ``case``, ``command``, ``continue``, ``else``, ``end``, ``eval``, ``exec``, ``for``, ``function``, ``if``, ``not``, ``or``, ``read``, ``return``, ``set``, ``status``, ``string``, ``switch``, ``test``, ``time``, and ``while``.
|
||||
|
||||
Care should be taken when creating a function of the same name as an existing shell builtin or common program. If the function behaves differently, it is very common for problems to occur within fish or in scripts written by others. Consider writing an :doc:`abbreviation <abbr>` if you are wanting to replace one tool with another for interactive use.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ The following options control how much is read and how it is stored:
|
||||
Marks the end of the line with the NUL character, instead of newline. This also disables interactive mode.
|
||||
|
||||
**-L** or **--line**
|
||||
Reads each line into successive variables, and stops after each variable has been filled. This cannot be combined with the ``--delimiter`` option.
|
||||
Reads each line into successive variables, and stops after each variable has been filled. This cannot be combined with the ``--null`` option, or options to control splitting like ``--delimiter``.
|
||||
|
||||
Without the ``--line`` option, ``read`` reads a single line of input from standard input, breaks it into tokens, and then assigns one token to each variable specified in *VARIABLES*. If there are more tokens than variables, the complete remainder is assigned to the last variable.
|
||||
|
||||
|
||||
@@ -11,8 +11,10 @@ Synopsis
|
||||
Description
|
||||
-----------
|
||||
|
||||
``set_color`` is used to control the color and styling of text in the terminal.
|
||||
*VALUE* describes that styling.
|
||||
``set_color`` controls the color and styling of text in the terminal.
|
||||
It writes non-printing color and text style escape sequences to standard output.
|
||||
|
||||
*VALUE* describes the styling.
|
||||
*VALUE* can be a reserved color name like **red** or an RGB color value given as 3 or 6 hexadecimal digits ("F27" or "FF2277").
|
||||
A special keyword **normal** resets text formatting to terminal defaults, however it is not recommended and the **--reset** option is preferred as it is less confusing and more future-proof.
|
||||
|
||||
@@ -93,7 +95,9 @@ Notes
|
||||
3. Because of the risk of confusion, ``set_color --reset`` is recommended over ``set_color normal``.
|
||||
4. Setting the background color only affects subsequently written characters. Fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
|
||||
5. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
|
||||
6. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
|
||||
6. If you use ``set_color`` in a command substitution or a pipe, these characters will also be captured.
|
||||
This may or may not be desirable.
|
||||
Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
@@ -150,7 +150,7 @@ By default, Fish searches the following for completions, using the first availab
|
||||
- A directory for systems administrators to install completions for all users on the system, usually ``/etc/fish/completions``;
|
||||
- A user-specified directory for third-party vendor completions, usually ``~/.local/share/fish/vendor_completions.d`` (controlled by the ``XDG_DATA_HOME`` environment variable);
|
||||
- A directory for third-party software vendors to ship their own completions for their software, usually ``/usr/share/fish/vendor_completions.d``;
|
||||
- The completions shipped with fish, usually installed in ``/usr/share/fish/completions``; and
|
||||
- The completions shipped with fish, which are stored in the fish program and can be seen with ``status list-files``; and
|
||||
- Completions automatically generated from the operating system's manual, usually stored in ``~/.cache/fish/generated_completions`` (controlled by ``XDG_CACHE_HOME`` environment variable).
|
||||
|
||||
These paths are controlled by parameters set at build, install, or run time, and may vary from the defaults listed above.
|
||||
|
||||
@@ -218,7 +218,7 @@ latex_engine = "xelatex"
|
||||
|
||||
def get_command_description(path, name):
|
||||
"""Return the description for a command, by parsing its synopsis line"""
|
||||
with open(path) as opened:
|
||||
with open(path, encoding="utf8") as opened:
|
||||
for line in opened:
|
||||
if line.startswith(name + " - "):
|
||||
_, desc = line.split(" - ", 1)
|
||||
|
||||
@@ -99,6 +99,15 @@ See :ref:`Shell variables <variables>` for more.
|
||||
|
||||
.. _bash-globs:
|
||||
|
||||
Variable defaults (``${my_variable:-"default value"}``)
|
||||
-------------------------------------------------------
|
||||
|
||||
Fish doesn't have ``${my_variable:-fallback}`` for providing default values to unset variables. Instead, you can set default values by checking whether the variable has been set yet::
|
||||
|
||||
# Ensure XDG_CONFIG_HOME is set or use a default value
|
||||
set -q XDG_CONFIG_HOME || set XDG_CONFIG_HOME $HOME/.config
|
||||
# now use XDG_CONFIG_HOME as normal
|
||||
|
||||
Wildcards (globs)
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ while others enable optional features and may be ignored by the terminal.
|
||||
The terminal must be able to parse Control Sequence Introducer (CSI) commands, Operating System Commands (OSC) and :ref:`optionally <term-compat-dcs-gnu-screen>` Device Control Strings (DCS).
|
||||
These are defined by ECMA-48.
|
||||
If a valid CSI, OSC or DCS sequence does not represent a command implemented by the terminal, the terminal must ignore it.
|
||||
For historical reasons, OSC sequences may be terminated with ``\x07`` instead of ``\e\\``.
|
||||
|
||||
Control sequences are denoted in a fish-like syntax.
|
||||
Special characters other than ``\`` are not escaped.
|
||||
@@ -333,7 +332,7 @@ Unicode Codepoints
|
||||
|
||||
By default, fish outputs the following non-ASCII characters::
|
||||
|
||||
× ► ¶ ⏎ • ● … μ – ’ ‘ “ ” ← → ↑ ↓
|
||||
× ► ¶ ⏎ • ● … μ ’ ‘ “ ”
|
||||
|
||||
as well as control pictures (U+2400 through U+241F),
|
||||
and locale-specific ones in :ref:`translated strings <variables-locale>`.
|
||||
and locale-specific ones in :ref:`translated messages <variables-locale>`.
|
||||
|
||||
@@ -738,7 +738,7 @@ See the documentation for :doc:`funced <cmds/funced>` and :doc:`funcsave <cmds/f
|
||||
Universal Variables
|
||||
-------------------
|
||||
|
||||
A universal variable is a variable whose value is shared across all instances of fish, now and in the future – even after a reboot. You can make a variable universal with ``set -U``::
|
||||
A universal variable is a variable whose value is shared across all instances of fish, now and in the future - even after a reboot. You can make a variable universal with ``set -U``::
|
||||
|
||||
> set -U EDITOR vim
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# Version set by updatecli.d/docker.yml
|
||||
FROM alpine:3.23
|
||||
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
|
||||
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
|
||||
RUN apk add --no-cache \
|
||||
cmake ninja \
|
||||
bash \
|
||||
cargo \
|
||||
g++ \
|
||||
gettext-dev \
|
||||
git \
|
||||
libintl \
|
||||
musl-dev \
|
||||
pcre2-dev \
|
||||
py3-pexpect \
|
||||
py3-pip \
|
||||
python3 \
|
||||
rust \
|
||||
rustfmt \
|
||||
sudo \
|
||||
tmux
|
||||
|
||||
RUN pip install --break-system-packages black
|
||||
|
||||
RUN addgroup -g 1000 fishuser
|
||||
|
||||
RUN adduser \
|
||||
--disabled-password \
|
||||
--gecos "" \
|
||||
--home "/home/fishuser" \
|
||||
--ingroup fishuser \
|
||||
--uid 1000 \
|
||||
fishuser
|
||||
|
||||
RUN mkdir -p /home/fishuser/fish-build \
|
||||
&& mkdir /fish-source \
|
||||
&& chown -R fishuser:fishuser /home/fishuser /fish-source
|
||||
|
||||
USER fishuser
|
||||
WORKDIR /home/fishuser
|
||||
|
||||
COPY fish_run_tests.sh /
|
||||
|
||||
ENV FISH_CHECK_LINT=false
|
||||
|
||||
CMD /fish_run_tests.sh
|
||||
@@ -1,5 +1,5 @@
|
||||
# Version set by updatecli.d/docker.yml
|
||||
FROM ubuntu:24.04
|
||||
FROM ubuntu:26.04
|
||||
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
|
||||
|
||||
ENV LANG=C.UTF-8
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Version set by updatecli.d/docker.yml
|
||||
FROM ubuntu:22.04
|
||||
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
|
||||
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install --no-install-recommends \
|
||||
cmake ninja-build \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
clang \
|
||||
curl \
|
||||
gettext \
|
||||
git \
|
||||
libpcre2-dev \
|
||||
locales \
|
||||
openssl \
|
||||
python3 \
|
||||
python3-pexpect \
|
||||
sudo \
|
||||
tmux \
|
||||
&& locale-gen en_US.UTF-8 \
|
||||
&& apt-get clean
|
||||
|
||||
RUN groupadd -g 1000 fishuser \
|
||||
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
|
||||
&& adduser fishuser sudo \
|
||||
&& mkdir -p /home/fishuser/fish-build \
|
||||
&& mkdir /fish-source \
|
||||
&& chown -R fishuser:fishuser /home/fishuser /fish-source
|
||||
|
||||
USER fishuser
|
||||
WORKDIR /home/fishuser
|
||||
|
||||
RUN curl --proto '=https' --tlsv1.2 -fsS https://sh.rustup.rs > /tmp/rustup.sh \
|
||||
&& sh /tmp/rustup.sh -y --no-modify-path
|
||||
ENV PATH=/home/fishuser/.cargo/bin:$PATH
|
||||
|
||||
COPY fish_run_tests.sh /
|
||||
|
||||
ENV FISH_CHECK_LINT=false
|
||||
|
||||
CMD /fish_run_tests.sh
|
||||
@@ -17,6 +17,9 @@ BuildRequires: /usr/bin/sphinx-build
|
||||
# OBS: add eg "FileProvides: /usr/bin/sphinx-build python3-sphinx python3-Sphinx" to project config
|
||||
BuildRequires: /usr/bin/man
|
||||
# OBS: add eg "FileProvides: /usr/bin/man man-db man" to project config
|
||||
# pkg-config is needed for the pcre2 crate to find the pcre2 system librar
|
||||
BuildRequires: /usr/bin/pkg-config
|
||||
# OBS: add eg "FileProvides: /usr/bin/pkg-config pkgconf-pkg-config pkg-config" to project config
|
||||
BuildRequires: cmake >= 3.15
|
||||
|
||||
# for tests
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
80820
localization/po/en.po
80820
localization/po/en.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,13 @@ name = "fish-shell"
|
||||
version = "0.0.0"
|
||||
# NOTE: versions for Python and Sphinx are specified for reproducibility.
|
||||
# Lower versions will work too.
|
||||
requires-python = ">=3.12"
|
||||
requires-python = ">=3.13" # updatecli.d/python.yml
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"sphinx>=9.1", # updatecli.d/python.yml
|
||||
"sphinx-markdown-builder",
|
||||
"sphinx>=9.1", # updatecli.d/python.yml
|
||||
"sphinx-markdown-builder",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
## --- WRITTEN MANUALLY ---
|
||||
|
||||
function __fish_cargo
|
||||
RUSTUP_AUTO_INSTALL=0 cargo --color=never $argv
|
||||
set -l tmp $__fish_cargo_wrapping cargo --color=never $argv
|
||||
RUSTUP_AUTO_INSTALL=0 $tmp
|
||||
end
|
||||
|
||||
set -l __fish_cargo_subcommands (__fish_cargo --list 2>&1 | string replace -rf '^\s+([^\s]+)\s*(.*)' '$1\t$2' | string escape)
|
||||
|
||||
complete -c cargo -f -c cargo -n __fish_use_subcommand -a "$__fish_cargo_subcommands"
|
||||
complete -c cargo -x -c cargo -n '__fish_seen_subcommand_from help' -a "$__fish_cargo_subcommands"
|
||||
complete -c cargo -f -n __fish_use_subcommand -a "$__fish_cargo_subcommands"
|
||||
complete -c cargo -x -n '__fish_seen_subcommand_from help' -a "$__fish_cargo_subcommands"
|
||||
|
||||
for x in bench b build c check rustc t test
|
||||
complete -c cargo -x -n "__fish_seen_subcommand_from $x" -l bench -a "(__fish_cargo bench --bench 2>&1 | string replace -rf '^\s+' '')"
|
||||
@@ -845,7 +846,7 @@ if command -q cargo-asm
|
||||
# Warning: this will build the project and can take time! We make sure to only call it if it's not a switch so completions
|
||||
# for --foo will always be fast.
|
||||
if command -q timeout
|
||||
complete -c cargo -n "__fish_seen_subcommand_from asm; and not __fish_is_switch" -xa "(timeout 1 __fish_cargo asm)"
|
||||
complete -c cargo -n "__fish_seen_subcommand_from asm; and not __fish_is_switch" -xa "(__fish_cargo_wrapping={timeout,1} __fish_cargo asm)"
|
||||
else
|
||||
complete -c cargo -n "__fish_seen_subcommand_from asm; and not __fish_is_switch" -xa "(__fish_cargo asm)"
|
||||
end
|
||||
|
||||
1
share/completions/chezmoi.fish
Normal file
1
share/completions/chezmoi.fish
Normal file
@@ -0,0 +1 @@
|
||||
chezmoi completion fish | source
|
||||
@@ -5,6 +5,9 @@ if date --version >/dev/null 2>/dev/null
|
||||
complete -c date -s I -l iso-8601 -d "Use ISO 8601 output format" -x -a "date hours minutes seconds"
|
||||
complete -c date -s s -l set -d "Set time" -x
|
||||
complete -c date -s R -l rfc-2822 -d "Output in RFC 2822 format"
|
||||
complete -c date -l rfc-3339=date -d "Output in RFC 3339 format with date precision"
|
||||
complete -c date -l rfc-3339=second -d "Output in RFC 3339 format with second precision"
|
||||
complete -c date -l rfc-3339=ns -d "Output in RFC 3339 format with nanosecond precision"
|
||||
complete -c date -s r -l reference -d "Display last modification time of file" -r
|
||||
complete -c date -s u -l utc -d "Print/set UTC time" -f
|
||||
complete -c date -l universal -d "Print/set UTC time" -f
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
# Completion for dive: https://github.com/wagoodman/dive
|
||||
|
||||
# Options
|
||||
complete -c dive -l ci -d "Skip the interactive TUI and validate against CI rules"
|
||||
complete -c dive -l ci-config -F -r -d "If CI=true in the environment, use the given yaml to drive validation rules"
|
||||
complete -c dive -l config -F -r -d "Config file"
|
||||
complete -c dive -s h -l help -d "Help for dive"
|
||||
complete -c dive -l highestUserWastedPercent -r -n "__fish_seen_argument -l ci" -d "Highest allowable percentage of bytes wasted"
|
||||
complete -c dive -l highestWastedBytes -r -n "__fish_seen_argument -l ci" -d "Highest allowable bytes wasted"
|
||||
complete -c dive -s i -l ignore-errors -d "Ignore image parsing errors and run the analysis anyway"
|
||||
complete -c dive -s j -l json -r -d "Skip the interactive TUI and write the layer analysis statistics to a given file"
|
||||
complete -c dive -l lowestEfficiency -r -d "Lowest allowable image efficiency"
|
||||
complete -c dive -l source -a "docker podman docker-archive" -d "The container engine to fetch the image from"
|
||||
complete -c dive -s v -l version -d "Display version number"
|
||||
|
||||
# Subcommands
|
||||
complete -c dive -a "build help version"
|
||||
complete -c dive -n __fish_use_subcommand -xa build -d "Build and analyze a Docker image from a Dockerfile"
|
||||
complete -c dive -n __fish_use_subcommand -xa help -d "Help about any command"
|
||||
complete -c dive -n __fish_use_subcommand -xa version -d "Print the version number and exit"
|
||||
complete -c dive -n "__fish_seen_subcommand_from help" -a "build help version"
|
||||
# Builtin options and subcommands
|
||||
dive completion fish | source
|
||||
|
||||
# Arguments
|
||||
complete -c dive -xa "(docker images --format '{{.Repository}}:{{.Tag}}' | command grep -v '<none>')"
|
||||
function __fish_docker_or_podman_image_tags
|
||||
command -v docker >/dev/null
|
||||
set --local docker_status $status
|
||||
command -v podman >/dev/null
|
||||
set --local podman_status $status
|
||||
|
||||
if test $docker_status -eq 0 && test $podman_status -eq 0
|
||||
docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | command grep -v '<none>' | sed 's#^#docker://#'
|
||||
podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | command grep -v '<none>' | sed 's#^#podman://#'
|
||||
else if test $docker_status -eq 0
|
||||
docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | command grep -v '<none>'
|
||||
else if test $podman_status -eq 0
|
||||
podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | command grep -v '<none>'
|
||||
end
|
||||
end
|
||||
complete -c dive -xa "(__fish_docker_or_podman_image_tags)"
|
||||
|
||||
@@ -1,19 +1,50 @@
|
||||
complete -c du -s a -l all -d "Write size for all files"
|
||||
complete -c du -l apparent-size -d "Print file size, not disk usage"
|
||||
complete -c du -s B -l block-size -d "Block size"
|
||||
complete -c du -s b -l bytes -d "Use 1B block size"
|
||||
complete -c du -s c -l total -d "Produce grand total"
|
||||
complete -c du -s D -l dereference-args -d "Dereference file symlinks"
|
||||
complete -c du -s h -l human-readable -d "Human readable sizes"
|
||||
complete -c du -s H -l si -d "Human readable sizes, powers of 1000"
|
||||
complete -c du -s k -d "Use 1kB block size"
|
||||
complete -c du -s l -l count-links -d "Count hard links multiple times"
|
||||
complete -c du -s L -l dereference -d "Dereference all symlinks"
|
||||
complete -c du -s S -l separate-dirs -d "Do not include subdirectory size"
|
||||
complete -c du -s s -l summarize -d "Display only a total for each argument"
|
||||
complete -c du -s x -l one-file-system -d "Skip other file systems"
|
||||
complete -c du -s X -l exclude-from -r -d "Exclude files that match pattern in file"
|
||||
complete -c du -l exclude -r -d "Exclude files that match pattern"
|
||||
complete -c du -l max-depth -r -d "Recursion limit"
|
||||
complete -c du -l help -d "Display help and exit"
|
||||
complete -c du -l version -d "Display version and exit"
|
||||
# test if we are using GNU du
|
||||
set -l is_gnu
|
||||
if du --version &>/dev/null
|
||||
set is_gnu --is-gnu
|
||||
end
|
||||
|
||||
# shared switches - short on both, long on GNU
|
||||
__fish_gnu_complete -c du -s a -l all -d "Write size for all files" $is_gnu
|
||||
__fish_gnu_complete -c du -s c -l total -d "Produce grand total" $is_gnu
|
||||
__fish_gnu_complete -c du -s d -l max-depth -x -d "Recursion limit" $is_gnu
|
||||
__fish_gnu_complete -c du -s h -l human-readable -d "Human readable sizes" $is_gnu
|
||||
__fish_gnu_complete -c du -s l -l count-links -d "Count hard links multiple times" $is_gnu
|
||||
__fish_gnu_complete -c du -s L -l dereference -d "Follow all symlinks" $is_gnu
|
||||
__fish_gnu_complete -c du -s P -l no-dereference -d "Do not follow symlinks (default)" $is_gnu
|
||||
__fish_gnu_complete -c du -s s -l summarize -d "Display only a total for each argument" $is_gnu
|
||||
__fish_gnu_complete -c du -s t -l threshold -x -d "Threshold size" $is_gnu
|
||||
__fish_gnu_complete -c du -s x -l one-file-system -d "Skip other file systems" $is_gnu
|
||||
|
||||
# shared switches without long options or with shared long options (--si)
|
||||
complete -c du -s H -d "Follow command-line symlinks only"
|
||||
complete -c du -s k -d "Use 1KB block size"
|
||||
complete -c du -s m -d "Use 1MB block size"
|
||||
complete -c du -l si -d "Human readable sizes, powers of 1000"
|
||||
|
||||
# platform-specific switches
|
||||
if test -n "$is_gnu"
|
||||
# GNU specific switches
|
||||
complete -c du -s 0 -l null -d "End each output line with NUL"
|
||||
complete -c du -l apparent-size -d "Print file size, not disk usage"
|
||||
complete -c du -s B -l block-size -x -a "K M G T P E KiB MiB GiB TiB PiB EiB KB MB GB TB PB EB" -d "Block size (e.g. 1M, KiB)"
|
||||
complete -c du -s b -l bytes -d "Use 1B block size"
|
||||
complete -c du -s D -l dereference-args -d "Follow command-line symlinks only"
|
||||
complete -c du -l files0-from -r -d "Summarize usage of NUL-terminated files in file"
|
||||
complete -c du -l inodes -d "List inode usage instead of block usage"
|
||||
complete -c du -s S -l separate-dirs -d "Do not include subdirectory size"
|
||||
complete -c du -l time -f -a "atime access use ctime status" -d "Show time as WORD instead of modification time"
|
||||
complete -c du -l time-style -x -a "full-iso long-iso iso" -d "Show times using style (or +FORMAT)"
|
||||
complete -c du -s X -l exclude-from -r -d "Exclude files that match pattern in file"
|
||||
complete -c du -l exclude -x -d "Exclude files that match pattern"
|
||||
complete -c du -l help -d "Display help and exit"
|
||||
complete -c du -l version -d "Display version and exit"
|
||||
else
|
||||
# macOS/BSD specific features
|
||||
complete -c du -s A -d "Display apparent size"
|
||||
complete -c du -s g -d "Use 1GB block size"
|
||||
complete -c du -s I -x -d "Ignore files matching mask"
|
||||
complete -c du -s n -d "Ignore nodump files"
|
||||
complete -c du -s B -x -d "Block size in bytes"
|
||||
#complete -c du -s r -d "Generate read error messages. The flag exists for portability"
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#function __fish_emerge_print_all_pkgs_with_version_compare -d 'Print completions for all packages including the version compare if that is already typed'
|
||||
# set -l version_comparator (commandline -t | string match -r '^[\'"]*[<>]\?=\?' | \
|
||||
# set -l version_comparator (commandline -t | string match -r '^[\'"]*[<>]\?=\?' |
|
||||
# sed -r 's/^[\'"]*(.*)/\1/g')
|
||||
# set -l sedstring
|
||||
#
|
||||
|
||||
@@ -1047,6 +1047,9 @@ complete -x -c git -n '__fish_git_using_command diff log show range-diff' -l ws-
|
||||
complete -f -c git -n '__fish_git_using_command fetch pull' -l unshallow -d 'Convert a shallow repository to a complete one'
|
||||
complete -f -c git -n '__fish_git_using_command fetch pull' -l set-upstream -d 'Add upstream (tracking) reference'
|
||||
|
||||
complete -x -c git -n '__fish_git_using_command fetch pull' -l recurse-submodules -a 'yes on-demand no' -d 'Recursively fetch submodules'
|
||||
complete -f -c git -n '__fish_git_using_command fetch pull' -l no-recurse-submodules -d 'Do not recursively fetch submodules'
|
||||
|
||||
#### fetch
|
||||
complete -f -c git -n __fish_git_needs_command -a fetch -d 'Download objects from another repo'
|
||||
# Suggest "repository", then "refspec" - this also applies to e.g. push/pull
|
||||
@@ -1560,11 +1563,11 @@ complete -f -c git -n '__fish_git_using_command describe' -l first-parent -d 'Fo
|
||||
|
||||
### diff
|
||||
complete -c git -n __fish_git_needs_command -a diff -d 'Show changes between commits and working tree'
|
||||
complete -c git -n '__fish_git_using_command diff' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_ranges)'
|
||||
complete -c git -n '__fish_git_using_command diff' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_complete_stashes)'
|
||||
complete -c git -n '__fish_git_using_command diff' -n 'not contains -- -- (commandline -xpc)' -n 'not __fish_git_contains_opt no-index' -ka '(__fish_git_ranges)'
|
||||
complete -c git -n '__fish_git_using_command diff' -n 'not contains -- -- (commandline -xpc)' -n 'not __fish_git_contains_opt no-index' -ka '(__fish_git_complete_stashes)'
|
||||
begin
|
||||
set -lx __fish_git_recent_commits_arg --all
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command diff'
|
||||
__fish_git_add_revision_completion -n '__fish_git_using_command diff' -n 'not __fish_git_contains_opt no-index'
|
||||
end
|
||||
complete -c git -n '__fish_git_using_command diff' -l cached -d 'Show diff of changes in the index'
|
||||
complete -c git -n '__fish_git_using_command diff' -l staged -d 'Show diff of changes in the index'
|
||||
@@ -1684,6 +1687,18 @@ complete -f -c git -n '__fish_git_using_command grep' -l untracked -d 'Search in
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l no-index -d 'Search files in current directory that is not managed by Git'
|
||||
complete -f -c git -n '__fish_git_using_command grep' -l recurse-submodules -d 'Recursively search in each submodule'
|
||||
|
||||
### history
|
||||
set -l git_history_commands reword split
|
||||
complete -f -c git -n __fish_git_needs_command -a history -d 'Rewrite history'
|
||||
complete -f -c git -n "__fish_git_using_command history" -n "not __fish_seen_subcommand_from $git_history_commands" -a reword -d 'Rewrite a commit message'
|
||||
complete -f -c git -n "__fish_git_using_command history" -n "not __fish_seen_subcommand_from $git_history_commands" -a split -d 'Split up a commit'
|
||||
|
||||
complete -f -c git -n '__fish_git_using_command history' -n '__fish_seen_subcommand_from reword split' -l dry-run -d 'Do not update references'
|
||||
complete -x -c git -n '__fish_git_using_command history' -n '__fish_seen_subcommand_from reword split' -l update-refs -a 'branches head'
|
||||
complete -x -c git -n '__fish_git_using_command history' -n '__fish_seen_subcommand_from reword' -ka '(__fish_git_recent_commits)'
|
||||
complete -x -c git -n '__fish_git_using_command history' -n '__fish_seen_subcommand_from split' -n 'not contains -- -- (commandline -xpc)' -ka '(__fish_git_recent_commits)'
|
||||
complete -F -c git -n '__fish_git_using_command history' -n '__fish_seen_subcommand_from split' -n 'contains -- -- (commandline -xpc)'
|
||||
|
||||
### init
|
||||
complete -f -c git -n __fish_git_needs_command -a init -d 'Create an empty git repository'
|
||||
complete -f -c git -n '__fish_git_using_command init' -s q -l quiet -d 'Only print error and warning messages'
|
||||
|
||||
@@ -157,6 +157,8 @@ function __fish_help_describe -a help_item
|
||||
return
|
||||
case fish_for_bash_users#test-test
|
||||
return
|
||||
case fish_for_bash_users#variable-defaults-my-variable-default-value
|
||||
return
|
||||
case fish_for_bash_users#variables
|
||||
return
|
||||
case fish_for_bash_users#wildcards-globs
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
function _justfile_targets
|
||||
just -l | tail -n +2 | string trim -l | string replace -r '(\s*#\s*)' '\t' | string replace -r '(\s*[\*\+][^\s]*)' ''
|
||||
end
|
||||
complete -c just -f -a '(_justfile_targets)'
|
||||
just --completions fish | source
|
||||
|
||||
1
share/completions/mise.fish
Normal file
1
share/completions/mise.fish
Normal file
@@ -0,0 +1 @@
|
||||
mise completion fish | source
|
||||
1
share/completions/niri.fish
Normal file
1
share/completions/niri.fish
Normal file
@@ -0,0 +1 @@
|
||||
niri completions fish | source
|
||||
@@ -411,8 +411,8 @@ complete -c sfdx -n '__fish_sfdx_using_command force:limits:api:display' -l logl
|
||||
complete $sfdx_looking -xa force:mdapi -d 'retrieve and deploy metadata using Metadata'
|
||||
|
||||
complete $sfdx_looking -xa force:mdapi:convert -d 'convert metadata from the Metadata API format into the source format'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:mdapi:convert' -s d -l outputdir -d 'the output directory to store the source–formatted files'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:mdapi:convert' -s r -l rootdir -d '(required) the root directory containing the Metadata API–formatted metadata'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:mdapi:convert' -s d -l outputdir -d 'the output directory to store the source-formatted files'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:mdapi:convert' -s r -l rootdir -d '(required) the root directory containing the Metadata API-formatted metadata'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:mdapi:convert' -l json -d 'format output as json'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:mdapi:convert' -l loglevel -d '[default: warn] logging level for this command invocation' -xa $sfdx_loglevels
|
||||
|
||||
@@ -820,7 +820,7 @@ complete -c sfdx -n '__fish_sfdx_using_command force:schema:sobject:list' -l log
|
||||
complete $sfdx_looking -xa force:source -d 'sync your project with your orgs'
|
||||
|
||||
complete $sfdx_looking -xa force:source:convert -d 'convert source into Metadata API format'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:source:conevrt' -s d -l outputdir -d 'output directory to store the Metadata API–formatted files in'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:source:conevrt' -s d -l outputdir -d 'output directory to store the Metadata API-formatted files in'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:source:conevrt' -s n -l packagename -d 'name of the package to associate with the metadata-formatted files'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:source:conevrt' -s r -l rootdir -d 'a source directory other than the default package to convert'
|
||||
complete -c sfdx -n '__fish_sfdx_using_command force:schema:convert' -l json -d 'format output as json'
|
||||
|
||||
@@ -1 +1 @@
|
||||
complete sudo-rs --wraps sudo
|
||||
__fish_complete_sudo sudo-rs
|
||||
|
||||
@@ -1,63 +1 @@
|
||||
#
|
||||
# Completion for sudo
|
||||
#
|
||||
|
||||
function __fish_sudo_print_remaining_args
|
||||
set -l tokens (commandline -xpc | string escape) (commandline -ct)
|
||||
set -e tokens[1]
|
||||
# These are all the options mentioned in the man page for Todd Miller's "sudo.ws" sudo (in that order).
|
||||
# If any other implementation has different options, this should be harmless, since they shouldn't be used anyway.
|
||||
set -l opts A/askpass b/background C/close-from= E/preserve-env='?'
|
||||
# Note that "-h" is both "--host" (which takes an option) and "--help" (which doesn't).
|
||||
# But `-h` as `--help` only counts when it's the only argument (`sudo -h`),
|
||||
# so any argument completion after that should take it as "--host".
|
||||
set -a opts e/edit g/group= H/set-home h/host= 1-help
|
||||
set -a opts i/login K/remove-timestamp k/reset-timestamp l/list n/non-interactive
|
||||
set -a opts P/preserve-groups p/prompt= S/stdin s/shell U/other-user=
|
||||
set -a opts u/user= T/command-timeout= V/version v/validate
|
||||
argparse -s $opts -- $tokens 2>/dev/null
|
||||
# The remaining argv is the subcommand with all its options, which is what
|
||||
# we want.
|
||||
if test -n "$argv"
|
||||
and not string match -qr '^-' $argv[1]
|
||||
string join0 -- $argv
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_sudo_no_subcommand
|
||||
not __fish_sudo_print_remaining_args >/dev/null
|
||||
end
|
||||
|
||||
function __fish_complete_sudo_subcommand
|
||||
set -l args (__fish_sudo_print_remaining_args | string split0)
|
||||
set -lx -a PATH /usr/local/sbin /sbin /usr/sbin
|
||||
__fish_complete_subcommand --commandline $args
|
||||
end
|
||||
|
||||
# All these options should be valid for GNU and OSX sudo
|
||||
complete -c sudo -n __fish_no_arguments -s h -d "Display help and exit"
|
||||
complete -c sudo -n __fish_no_arguments -s V -d "Display version information and exit"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s A -d "Ask for password via the askpass or \$SSH_ASKPASS program"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s C -d "Close all file descriptors greater or equal to the given number" -xa "0 1 2 255"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s E -d "Preserve environment"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s H -d "Set home"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s K -d "Remove the credential timestamp entirely"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s P -d "Preserve group vector"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s S -d "Read password from stdin"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s b -d "Run command in the background"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s e -rF -d Edit
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s g -a "(__fish_complete_groups)" -x -d "Run command as group"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s i -d "Run a login shell"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s k -d "Reset or ignore the credential timestamp"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s l -d "List the allowed and forbidden commands for the given user"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s n -d "Do not prompt for a password - if one is needed, fail"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s p -d "Specify a custom password prompt"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s s -d "Run the given command in a shell"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s u -a "(__fish_complete_users)" -x -d "Run command as user"
|
||||
complete -c sudo -n __fish_sudo_no_subcommand -s v -n __fish_no_arguments -d "Validate the credentials, extending timeout"
|
||||
|
||||
# Complete the command we are executed under sudo
|
||||
complete -c sudo -x -n 'not __fish_seen_argument -s e' -a "(__fish_complete_sudo_subcommand)"
|
||||
__fish_complete_sudo sudo
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
set -l systemd_version (systemctl --version | string match "systemd*" | string replace -r "\D*(\d+)\D.*" '$1')
|
||||
set -l commands list-units list-sockets start stop reload restart try-restart reload-or-restart reload-or-try-restart \
|
||||
isolate kill is-active is-failed status show get-cgroup-attr set-cgroup-attr unset-cgroup-attr set-cgroup help \
|
||||
reset-failed list-unit-files enable disable is-enabled reenable preset mask unmask link load list-jobs cancel dump \
|
||||
list-dependencies snapshot delete daemon-reload daemon-reexec show-environment set-environment unset-environment \
|
||||
reset-failed list-unit-files enable disable is-enabled reenable preset mask unmask link list-jobs cancel \
|
||||
list-dependencies daemon-reload daemon-reexec show-environment set-environment unset-environment \
|
||||
default rescue emergency halt poweroff reboot kexec exit suspend suspend-then-hibernate hibernate hybrid-sleep switch-root \
|
||||
list-timers set-property import-environment get-default list-automounts is-system-running try-reload-or-restart freeze \
|
||||
thaw mount-image bind clean
|
||||
if test $systemd_version -gt 208 2>/dev/null
|
||||
set commands $commands cat
|
||||
if test $systemd_version -gt 217 2>/dev/null
|
||||
set commands $commands edit
|
||||
end
|
||||
thaw mount-image bind clean set-default cat list-machines preset-all add-wants add-requires edit
|
||||
if test $systemd_version -gt 243 2>/dev/null
|
||||
set commands $commands log-level log-target service-watchdogs
|
||||
end
|
||||
if test $systemd_version -gt 246 2>/dev/null
|
||||
set commands $commands service-log-level service-log-target
|
||||
end
|
||||
if test $systemd_version -gt 253 2>/dev/null
|
||||
set commands $commands list-paths soft-reboot whoami
|
||||
end
|
||||
if test $systemd_version -gt 255 2>/dev/null
|
||||
set commands $commands sleep
|
||||
end
|
||||
|
||||
set -l types services sockets mounts service_paths targets automounts timers
|
||||
|
||||
function __fish_systemd_properties
|
||||
@@ -28,23 +35,51 @@ end
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a "$commands"
|
||||
|
||||
#### Units commands
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a start -d 'Start one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a stop -d 'Stop one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a restart -d 'Restart one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a reload-or-restart -d 'Reload units if supported or restart them'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a try-reload-or-restart -d 'Reload units if supported or restart them, if running'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a status -d 'Runtime status about one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a enable -d 'Enable one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a disable -d 'Disable one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a isolate -d 'Start a unit and dependencies and disable all others'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a set-default -d 'Set the default target to boot into'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a get-default -d 'Show the default target to boot into'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a set-property -d 'Sets one or more properties of a unit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a list-automounts -d 'List automount units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a is-system-running -d 'Return if system is running/starting/degraded'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a freeze -d 'Freeze units with the cgroup freezer'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a thaw -d 'Unfreeze frozen units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a add-requires -d 'Add Requires dependencies to a target unit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a add-wants -d 'Add Wants dependencies to a target unit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a bind -d 'Bind mount a path into the mount namespace of a unit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a cancel -d 'Cancel one or more jobs'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a cat -d 'Show the backing files of one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a clean -d 'Remove config/state/logs for the given units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a daemon-reload -d 'Reload the configuration of the system manager'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a default -d 'Enter and isolate the default mode'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a disable -d 'Disable one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a edit -d 'Edit a unit file or drop-in snippet'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a enable -d 'Enable one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a emergency -d 'Enter and isolate emergency mode'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a exit -d 'Ask the service manager to exit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a freeze -d 'Freeze units with the cgroup freezer'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a get-default -d 'Show the default target to boot into'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a help -d 'Show the manual page for a unit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a import-environment -d 'Import environment variables into the service manager'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a isolate -d 'Start a unit and dependencies and disable all others'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a is-system-running -d 'Return if system is running/starting/degraded'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a kexec -d 'Reboot via kexec'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a kill -d 'Kill one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a link -d 'Link a unit file into the unit search path'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a list-automounts -d 'List automount units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a list-machines -d 'List the host and all running local containers'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a mask -d 'Prevent one or more units from starting'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a mount-image -d 'Mount an image into the mount namespace of a unit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a preset -d 'Enable/disable a unit depending on preset configuration'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a preset-all -d 'Enable/disable all units depending on preset configuration'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a reenable -d 'Disable the enable one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a reload -d 'Request a unit reload its configuration'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a reload-or-restart -d 'Reload units if supported or restart them'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a rescue -d 'Enter and isolate rescue mode'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a restart -d 'Restart one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a set-default -d 'Set the default target to boot into'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a set-property -d 'Sets one or more properties of a unit'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a show -d 'Show properties of one or more units, jobs, or the manager itself'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a show-environment -d 'Dump the systemd manager environment block'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a soft-reboot -d 'Reboot userspace'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a start -d 'Start one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a status -d 'Runtime status about one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a stop -d 'Stop one or more units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a thaw -d 'Unfreeze frozen units'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a try-reload-or-restart -d 'Reload units if supported or restart them, if running'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a unmask -d 'Stop preventing one or more units from starting'
|
||||
complete -f -c systemctl -n "not __fish_seen_subcommand_from $commands" -a whoami -d 'Query the unit that owns a process'
|
||||
|
||||
# Command completion done via argparse.
|
||||
complete -c systemctl -a '(__fish_systemctl)' -f
|
||||
@@ -87,13 +122,9 @@ complete -x -c systemctl -s M -l machine -d 'Execute operation on a VM or contai
|
||||
complete -f -c systemctl -s h -l help -d 'Print a short help and exit'
|
||||
complete -f -c systemctl -l version -d 'Print a short version and exit'
|
||||
complete -f -c systemctl -l no-pager -d 'Do not pipe output into a pager'
|
||||
|
||||
# New options since systemd 220
|
||||
if test $systemd_version -gt 219 2>/dev/null
|
||||
complete -f -c systemctl -l firmware-setup -n "__fish_seen_subcommand_from reboot" -d "Reboot to EFI setup"
|
||||
complete -f -c systemctl -l now -n "__fish_seen_subcommand_from enable" -d "Also start unit"
|
||||
complete -f -c systemctl -l now -n "__fish_seen_subcommand_from disable mask" -d "Also stop unit"
|
||||
end
|
||||
complete -f -c systemctl -l firmware-setup -n "__fish_seen_subcommand_from reboot" -d "Reboot to EFI setup"
|
||||
complete -f -c systemctl -l now -n "__fish_seen_subcommand_from enable" -d "Also start unit"
|
||||
complete -f -c systemctl -l now -n "__fish_seen_subcommand_from disable mask" -d "Also stop unit"
|
||||
|
||||
# New options since systemd 242
|
||||
if test $systemd_version -ge 242 2>/dev/null
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# Returns 0 if the command has not had a subcommand yet
|
||||
# Does not currently account for -chdir
|
||||
function __fish_terraform_needs_command
|
||||
set -l cmd (commandline -xpc)
|
||||
|
||||
if test (count $cmd) -eq 1
|
||||
return 0
|
||||
set -e cmd[1]
|
||||
for arg in $cmd
|
||||
switch $arg
|
||||
case '-chdir=*'
|
||||
continue
|
||||
case '*'
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
return 1
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_terraform_workspaces
|
||||
@@ -17,6 +20,7 @@ end
|
||||
# general options
|
||||
complete -f -c terraform -n "not __fish_terraform_needs_command" -o version -d "Print version information"
|
||||
complete -f -c terraform -o help -d "Show help"
|
||||
complete -x -c terraform -n __fish_terraform_needs_command -o chdir -a "(__fish_complete_directories)" -d "Switch to a different working directory before executing"
|
||||
|
||||
### apply/destroy
|
||||
set -l apply apply destroy
|
||||
@@ -40,6 +44,10 @@ complete -r -c terraform -n "__fish_seen_subcommand_from console" -o state -d "P
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from console" -o var -d "Set a variable in the Terraform configuration"
|
||||
complete -r -c terraform -n "__fish_seen_subcommand_from console" -o var-file -d "Set variables from a file"
|
||||
|
||||
### force-unlock
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a force-unlock -d "Release a stuck lock on the current workspace"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from force-unlock" -o force -d "Don't ask for input for unlock confirmation"
|
||||
|
||||
### fmt
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a fmt -d "Rewrite config files to canonical format"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from fmt" -o list=false -d "Don't list files whose formatting differs"
|
||||
@@ -104,6 +112,18 @@ complete -f -c terraform -n "__fish_seen_subcommand_from login" -a "(__fish_prin
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a logout -d "Removes auth token for the given hostname"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from logout" -a "(__fish_print_hostnames)"
|
||||
|
||||
### metadata
|
||||
set -l metadata_commands functions
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a metadata -d "Metadata related commands"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from metadata && not __fish_seen_subcommand_from $metadata_commands" -a functions -d "Show signatures and descriptions for the available functions"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from metadata && __fish_seen_subcommand_from functions" -o json -d "Print function signatures in JSON format"
|
||||
|
||||
### modules
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a modules -d "Show all declared modules in a working directory"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from modules" -o json -d "Output declared modules in a machine-readable format"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from modules" -o var -d "Set a variable in the Terraform configuration"
|
||||
complete -r -c terraform -n "__fish_seen_subcommand_from modules" -o var-file -d "Set variables from a file"
|
||||
|
||||
### output
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a output -d "Read an output from a state file"
|
||||
complete -r -c terraform -n "__fish_seen_subcommand_from output" -o state -d "Path to the state file to read"
|
||||
@@ -138,9 +158,17 @@ complete -r -c terraform -n "__fish_seen_subcommand_from $plan" -o var-file -d "
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a providers -d "Print tree of modules with their provider requirements"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from providers" -a "lock mirror schema"
|
||||
|
||||
### query
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a query -d "Search and list remote infrastructure with Terraform"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from query" -o var -d "Set a variable in the Terraform configuration"
|
||||
complete -r -c terraform -n "__fish_seen_subcommand_from query" -o var-file -d "Set variables from a file"
|
||||
complete -r -c terraform -n "__fish_seen_subcommand_from query" -o generate-config-out -d "Generate import and resource blocks for found results"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from query" -o json -d "Produce machine readable output in JSON format"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from query" -o no-color -d "If specified, output won't contain any color"
|
||||
|
||||
### refresh
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a refresh -d "Update local state file against real resources"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from $apply" -o compact-warnings -d "Show only error summaries"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from refresh" -o compact-warnings -d "Show only error summaries"
|
||||
complete -r -c terraform -n "__fish_seen_subcommand_from refresh" -o backup -d "Path to backup the existing state file"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from refresh" -o input=true -d "Ask for input for variables if not directly set"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from refresh" -o lock=false -d "Don't hold a state lock"
|
||||
@@ -155,7 +183,21 @@ complete -r -c terraform -n "__fish_seen_subcommand_from refresh" -o var-file -d
|
||||
### show
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a show -d "Inspect Terraform state or plan"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from show" -o no-color -d "If specified, output won't contain any color"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from validate" -o json -d "Produce output in JSON format"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from show" -o json -d "Produce output in JSON format"
|
||||
|
||||
### stacks
|
||||
set -l stacks_commands init providers-lock validate create list version fmt configuration deployment-group deployment-run
|
||||
complete -f -c terraform -n __fish_terraform_needs_command -a stacks -d "Manage HCP Terraform stack operations"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a init -d "Prepare the configuration directory for further commands"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a providers-lock -d "Write out dependency locks for the configured providers"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a validate -d "Check whether the configuration is valid"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a create -d "Create a stack"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a list -d "List stacks for a given organization and/or project"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a version -d "Show the current Stacks Plugin version"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a fmt -d "Reformat Terraform Stacks configuration"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a configuration -d "Manage stack configuration"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a deployment-group -d "Manage deployment groups"
|
||||
complete -f -c terraform -n "__fish_seen_subcommand_from stacks && not __fish_seen_subcommand_from $stacks_commands" -a deployment-run -d "Manage deployment runs"
|
||||
|
||||
### state
|
||||
complete -r -c terraform -n __fish_terraform_needs_command -a state -d "Advanced state management"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user