cdf-shell 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cdf_shell-0.1.0/.github/workflows/release.yml +75 -0
- cdf_shell-0.1.0/.github/workflows/test.yml +87 -0
- cdf_shell-0.1.0/.gitignore +11 -0
- cdf_shell-0.1.0/LICENSE +21 -0
- cdf_shell-0.1.0/Makefile +19 -0
- cdf_shell-0.1.0/PKG-INFO +150 -0
- cdf_shell-0.1.0/PKGBUILD +39 -0
- cdf_shell-0.1.0/README.md +140 -0
- cdf_shell-0.1.0/build-constraints.in +1 -0
- cdf_shell-0.1.0/build-constraints.txt +24 -0
- cdf_shell-0.1.0/pyproject.toml +58 -0
- cdf_shell-0.1.0/src/cdf/__init__.py +0 -0
- cdf_shell-0.1.0/src/cdf/__main__.py +8 -0
- cdf_shell-0.1.0/src/cdf/app.py +58 -0
- cdf_shell-0.1.0/src/cdf/cli.py +215 -0
- cdf_shell-0.1.0/src/cdf/config.py +178 -0
- cdf_shell-0.1.0/src/cdf/display.py +15 -0
- cdf_shell-0.1.0/src/cdf/errors.py +9 -0
- cdf_shell-0.1.0/src/cdf/picker.py +145 -0
- cdf_shell-0.1.0/src/cdf/shell/cdf.fish +30 -0
- cdf_shell-0.1.0/src/cdf/shell/cdf.sh +33 -0
- cdf_shell-0.1.0/src/cdf/walker.py +152 -0
- cdf_shell-0.1.0/tests/shell/test_cdf_fish.py +119 -0
- cdf_shell-0.1.0/tests/shell/test_cdf_sh.py +145 -0
- cdf_shell-0.1.0/tests/test_app.py +121 -0
- cdf_shell-0.1.0/tests/test_cli.py +401 -0
- cdf_shell-0.1.0/tests/test_config.py +339 -0
- cdf_shell-0.1.0/tests/test_display.py +18 -0
- cdf_shell-0.1.0/tests/test_picker.py +357 -0
- cdf_shell-0.1.0/tests/test_walker.py +411 -0
- cdf_shell-0.1.0/uv.lock +610 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: release-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
uses: ./.github/workflows/test.yml
|
|
18
|
+
|
|
19
|
+
build:
|
|
20
|
+
needs: test
|
|
21
|
+
runs-on: ubuntu-24.04
|
|
22
|
+
timeout-minutes: 10
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout
|
|
25
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
|
|
29
|
+
- name: Install uv
|
|
30
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
31
|
+
with:
|
|
32
|
+
version: "0.12.7"
|
|
33
|
+
python-version: "3.14.7"
|
|
34
|
+
|
|
35
|
+
- name: Verify tag matches package version
|
|
36
|
+
run: |
|
|
37
|
+
tag_version="${GITHUB_REF_NAME#v}"
|
|
38
|
+
# --no-project: reading pyproject.toml needs no environment, so don't
|
|
39
|
+
# sync one (which could also rewrite a stale uv.lock instead of failing).
|
|
40
|
+
pkg_version="$(uv run --no-project python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
|
|
41
|
+
if [ "$tag_version" != "$pkg_version" ]; then
|
|
42
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject.toml version $pkg_version"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Build backend pinned by version and hash (build-constraints.txt), so the
|
|
47
|
+
# published artifacts don't depend on whatever hatchling is newest today.
|
|
48
|
+
- name: Build distributions
|
|
49
|
+
run: make build
|
|
50
|
+
|
|
51
|
+
- name: Upload distributions
|
|
52
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
53
|
+
with:
|
|
54
|
+
name: dist
|
|
55
|
+
path: dist/
|
|
56
|
+
retention-days: 7
|
|
57
|
+
|
|
58
|
+
publish:
|
|
59
|
+
needs: build
|
|
60
|
+
runs-on: ubuntu-24.04
|
|
61
|
+
timeout-minutes: 10
|
|
62
|
+
environment:
|
|
63
|
+
name: pypi
|
|
64
|
+
url: https://pypi.org/project/cdf-shell/
|
|
65
|
+
permissions:
|
|
66
|
+
id-token: write
|
|
67
|
+
steps:
|
|
68
|
+
- name: Download distributions
|
|
69
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
70
|
+
with:
|
|
71
|
+
name: dist
|
|
72
|
+
path: dist/
|
|
73
|
+
|
|
74
|
+
- name: Publish to PyPI
|
|
75
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
# release.yml runs this same job before building, so the two can't drift.
|
|
8
|
+
workflow_call:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: test-${{ github.workflow }}-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
test:
|
|
19
|
+
runs-on: ubuntu-24.04
|
|
20
|
+
timeout-minutes: 10
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
# Oldest supported (requires-python) through newest, exact patch releases.
|
|
25
|
+
python: ["3.11.16", "3.12.14", "3.13.15", "3.14.7"]
|
|
26
|
+
# Latest fzf everywhere, plus the oldest supported one (Ubuntu 24.04's)
|
|
27
|
+
# once; the real-fzf picker tests skip if fzf is missing, so it's pinned.
|
|
28
|
+
fzf: ["0.74.4"]
|
|
29
|
+
include:
|
|
30
|
+
- python: "3.11.16"
|
|
31
|
+
fzf: "0.44.1"
|
|
32
|
+
steps:
|
|
33
|
+
- name: Checkout
|
|
34
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
35
|
+
with:
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
|
|
38
|
+
- name: Install uv
|
|
39
|
+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
40
|
+
with:
|
|
41
|
+
version: "0.12.7"
|
|
42
|
+
python-version: ${{ matrix.python }}
|
|
43
|
+
|
|
44
|
+
- name: Install dependencies
|
|
45
|
+
run: uv sync --locked
|
|
46
|
+
|
|
47
|
+
# Pinned to the versions the shell wrapper tests were verified with;
|
|
48
|
+
# the tests skip a shell that is missing, so this must not silently drift.
|
|
49
|
+
- name: Install zsh 5.9 and fish 4.9.3
|
|
50
|
+
env:
|
|
51
|
+
FISH_VERSION: 4.9.3
|
|
52
|
+
FISH_SHA256: 8f643d10ad1abb0ef7072764c01ceb5cf61b2d373fda400cf60f0bad69b7e095
|
|
53
|
+
run: |
|
|
54
|
+
sudo apt-get update
|
|
55
|
+
sudo apt-get install -y --no-install-recommends zsh
|
|
56
|
+
if ! zsh --version | grep -q '^zsh 5\.9 '; then
|
|
57
|
+
echo "::error::expected zsh 5.9, got: $(zsh --version)"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
curl -fsSL -o "$RUNNER_TEMP/fish.tar.xz" \
|
|
61
|
+
"https://github.com/fish-shell/fish-shell/releases/download/${FISH_VERSION}/fish-${FISH_VERSION}-linux-x86_64.tar.xz"
|
|
62
|
+
echo "${FISH_SHA256} $RUNNER_TEMP/fish.tar.xz" | sha256sum -c -
|
|
63
|
+
mkdir -p "$HOME/.local/bin"
|
|
64
|
+
tar -xf "$RUNNER_TEMP/fish.tar.xz" -C "$HOME/.local/bin"
|
|
65
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
66
|
+
"$HOME/.local/bin/fish" --version
|
|
67
|
+
|
|
68
|
+
- name: Install fzf ${{ matrix.fzf }}
|
|
69
|
+
env:
|
|
70
|
+
FZF_VERSION: ${{ matrix.fzf }}
|
|
71
|
+
run: |
|
|
72
|
+
# Release tags changed from "X.Y.Z" to "vX.Y.Z" along the way.
|
|
73
|
+
case "$FZF_VERSION" in
|
|
74
|
+
0.44.1) tag=0.44.1; sha=73d7946f08dae8a330f4233e3eeb71c4ffc411412e3fe8488d608dde3f7a74bd ;;
|
|
75
|
+
0.74.4) tag=v0.74.4; sha=05e6813a337cc722c3ed07e54a764b75cc5d671e2e60459db0ba696ee5fa7504 ;;
|
|
76
|
+
*) echo "::error::no pinned checksum for fzf $FZF_VERSION"; exit 1 ;;
|
|
77
|
+
esac
|
|
78
|
+
curl -fsSL -o "$RUNNER_TEMP/fzf.tar.gz" \
|
|
79
|
+
"https://github.com/junegunn/fzf/releases/download/${tag}/fzf-${FZF_VERSION}-linux_amd64.tar.gz"
|
|
80
|
+
echo "${sha} $RUNNER_TEMP/fzf.tar.gz" | sha256sum -c -
|
|
81
|
+
mkdir -p "$HOME/.local/bin"
|
|
82
|
+
tar -xzf "$RUNNER_TEMP/fzf.tar.gz" -C "$HOME/.local/bin"
|
|
83
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
84
|
+
"$HOME/.local/bin/fzf" --version | grep -q "^${FZF_VERSION} "
|
|
85
|
+
|
|
86
|
+
- name: Run test suite
|
|
87
|
+
run: make test
|
cdf_shell-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Felipe Ruhland
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cdf_shell-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.PHONY: run test lint build build-constraints
|
|
2
|
+
|
|
3
|
+
run:
|
|
4
|
+
uv run python -m cdf
|
|
5
|
+
|
|
6
|
+
test:
|
|
7
|
+
PYTHONDONTWRITEBYTECODE=1 uv run pytest
|
|
8
|
+
|
|
9
|
+
lint:
|
|
10
|
+
uv run ruff check .
|
|
11
|
+
|
|
12
|
+
# Build with the hash-pinned backend from build-constraints.txt (same as CI).
|
|
13
|
+
build:
|
|
14
|
+
uv build --build-constraints build-constraints.txt --require-hashes
|
|
15
|
+
|
|
16
|
+
# Re-lock build-constraints.txt after changing build-constraints.in.
|
|
17
|
+
build-constraints:
|
|
18
|
+
uv pip compile build-constraints.in --generate-hashes --universal \
|
|
19
|
+
--python-version 3.11 --no-header -o build-constraints.txt
|
cdf_shell-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cdf-shell
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive git-project cd/find
|
|
5
|
+
Author-email: Felipe Ruhland <pypi@feliperuhland.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# cdf
|
|
12
|
+
|
|
13
|
+
`cdf` ("cd" + "find") interactively searches the directory tree below your
|
|
14
|
+
current working directory for a term, and `cd`s into the directory you
|
|
15
|
+
select.
|
|
16
|
+
|
|
17
|
+
- Search scope is always the current working directory downward — never the
|
|
18
|
+
whole filesystem, never a cached/global index.
|
|
19
|
+
- By default it only matches git repository roots and stops descending once
|
|
20
|
+
it finds one (pass `--no-git` to walk every directory instead).
|
|
21
|
+
- Symlinked directories are listed (in git mode, only if they point at a
|
|
22
|
+
repository) but never descended into, so symlink loops can't hang the walk.
|
|
23
|
+
- Selection is powered by [`fzf`](https://github.com/junegunn/fzf) 0.44 or
|
|
24
|
+
newer, which must be on `PATH`. Your `FZF_DEFAULT_OPTS` (theme, layout,
|
|
25
|
+
`--print-query`, `--accept-nth`, ...) are respected.
|
|
26
|
+
- Results are shown (and fuzzy-matched) relative to the search root, so the
|
|
27
|
+
root's own path doesn't match every query.
|
|
28
|
+
- Directory names are shown with terminal control characters (escape
|
|
29
|
+
sequences, newlines, tabs) replaced by `?`, so a hostile name in a cloned
|
|
30
|
+
repo can't mess with your terminal; the directory you pick is still the
|
|
31
|
+
exact one on disk.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
### From source
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
uv sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This installs the `cdf` console script into `.venv/bin/`.
|
|
42
|
+
|
|
43
|
+
### Arch Linux
|
|
44
|
+
|
|
45
|
+
A `PKGBUILD` is provided at the repo root, sourcing the tarball from this
|
|
46
|
+
repo's own `v*.*.*` git tags:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
makepkg -si
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`cdf` (the binary) only *resolves* a path and prints it to stdout — a Python
|
|
53
|
+
process can't change its parent shell's working directory. Load the shell
|
|
54
|
+
integration so the `cdf` command actually `cd`s for you:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
# ~/.bashrc
|
|
58
|
+
eval "$(command cdf --init bash)"
|
|
59
|
+
# ~/.zshrc
|
|
60
|
+
eval "$(command cdf --init zsh)"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```fish
|
|
64
|
+
# ~/.config/fish/config.fish
|
|
65
|
+
command cdf --init fish | source
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This defines a shell function also named `cdf`, which takes priority over
|
|
69
|
+
the binary in your shell and calls it internally via `command cdf`. Make
|
|
70
|
+
sure the `cdf` binary itself is on `PATH` (e.g. via `uv tool install .`, or
|
|
71
|
+
by adding `.venv/bin` to `PATH`).
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
cdf [term ...] [-p/--path ROOT] [--git/--no-git] [-a/--all|--no-all] [-d/--max-depth N]
|
|
77
|
+
[-x/--one-file-system|--no-one-file-system] [-c/--config FILE]
|
|
78
|
+
cdf --init {bash,zsh,fish}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Flag | Default | Meaning |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `term ...` | `""` | initial fuzzy filter text passed to `fzf` (several words are joined with spaces) |
|
|
84
|
+
| `-p, --path` | cwd | root to search from |
|
|
85
|
+
| `--git` / `--no-git` | `--git` | only match git repository roots |
|
|
86
|
+
| `-a, --all` / `--no-all` | `--no-all` | include hidden/dot directories |
|
|
87
|
+
| `-d, --max-depth N` | no limit | only look `N` levels below the root (`0` = no limit, to lift a limit set in the config file) |
|
|
88
|
+
| `-x, --one-file-system` / `--no-one-file-system` | off | list mount points but don't descend into them, like `find -xdev` |
|
|
89
|
+
| `-c, --config` | `$CDF_CONFIG_PATH`, else first existing default | config file path |
|
|
90
|
+
| `--init SHELL` | | print the shell integration for `bash`, `zsh` or `fish` and exit |
|
|
91
|
+
|
|
92
|
+
Run `cdf --help` for the full reference, or `cdf --version` (the shell
|
|
93
|
+
function special-cases `--help`/`-h`/`--version`/`--init` to pass them straight
|
|
94
|
+
through instead of attempting to `cd` into their output).
|
|
95
|
+
|
|
96
|
+
### Exit codes
|
|
97
|
+
|
|
98
|
+
- `0` — a directory was selected
|
|
99
|
+
- `1` — nothing was selected (cancelled in `fzf`, or no candidates found, in
|
|
100
|
+
which case a hint is printed on stderr)
|
|
101
|
+
- `2` — an error occurred (message on stderr) — e.g. `fzf` missing, an
|
|
102
|
+
unreadable search root, or a bad config file
|
|
103
|
+
- `130` — interrupted with Ctrl-C
|
|
104
|
+
|
|
105
|
+
## Config file
|
|
106
|
+
|
|
107
|
+
The config file path is resolved in this order:
|
|
108
|
+
|
|
109
|
+
1. `-c/--config FILE` — must exist, or it's an error.
|
|
110
|
+
2. `$CDF_CONFIG_PATH` — must exist, or it's an error.
|
|
111
|
+
3. Otherwise, the first of these that exists (silently skipped if none do):
|
|
112
|
+
`$XDG_CONFIG_HOME/cdf/cdf.conf` (default `~/.config/cdf/cdf.conf`), then
|
|
113
|
+
`~/cdf.conf`.
|
|
114
|
+
|
|
115
|
+
All keys are optional (TOML syntax, despite the `.conf` extension):
|
|
116
|
+
|
|
117
|
+
```toml
|
|
118
|
+
path = "~/projects" # string, expanded with ~; relative paths are relative to this file
|
|
119
|
+
git = true # boolean
|
|
120
|
+
hidden = false # boolean
|
|
121
|
+
ignore = ["node_modules", "__pycache__", ".venv"] # glob patterns of directory names to skip
|
|
122
|
+
max_depth = 0 # non-negative integer, 0 = no limit
|
|
123
|
+
one_file_system = false # boolean
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`ignore` replaces the default list shown above (set `ignore = []` to skip
|
|
127
|
+
nothing). Patterns match one directory name at a time, so they can't contain
|
|
128
|
+
`/`. `.git` directories are never descended into regardless.
|
|
129
|
+
|
|
130
|
+
`one_file_system` is off by default because btrfs subvolumes (and some other
|
|
131
|
+
setups) report their own device IDs, so turning it on would hide those
|
|
132
|
+
directories. Turn it on if you run `cdf --no-git` from places like `/` or `~`
|
|
133
|
+
that have network or FUSE mounts below them, which can be slow or hang.
|
|
134
|
+
|
|
135
|
+
Unknown keys, and values of the wrong type (e.g. `git = "false"` instead of
|
|
136
|
+
`git = false`), are rejected with an error, to catch typos early.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
make run # uv run python -m cdf
|
|
142
|
+
make test # pytest + ruff + ruff-format + mypy (strict) + coverage, gated at 100%
|
|
143
|
+
make lint # uv run ruff check .
|
|
144
|
+
make build # sdist + wheel, with the build backend pinned by hash (build-constraints.txt)
|
|
145
|
+
make build-constraints # re-lock build-constraints.txt after editing build-constraints.in
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Supported Python: 3.11 through 3.14; CI runs the full `make test` gate on
|
|
149
|
+
each, with the bash/zsh/fish wrapper tests running against pinned zsh 5.9
|
|
150
|
+
and fish 4.9.3.
|
cdf_shell-0.1.0/PKGBUILD
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Maintainer: Felipe Ruhland <archlinux@feliperuhland.com>
|
|
2
|
+
|
|
3
|
+
pkgname=cdf-shell
|
|
4
|
+
pkgver=0.1.0
|
|
5
|
+
pkgrel=1
|
|
6
|
+
pkgdesc="Interactively search the directory tree below the cwd and cd into the result"
|
|
7
|
+
arch=('any')
|
|
8
|
+
url="https://github.com/feliperuhland/cdf-shell"
|
|
9
|
+
license=('MIT')
|
|
10
|
+
depends=('python' 'fzf>=0.44')
|
|
11
|
+
makedepends=('python-build' 'python-hatchling' 'python-installer')
|
|
12
|
+
checkdepends=('python-pytest')
|
|
13
|
+
_pkgsrc="${pkgname}-${pkgver}"
|
|
14
|
+
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz")
|
|
15
|
+
# Placeholder — v${pkgver} isn't tagged upstream yet, so this can't be computed
|
|
16
|
+
# for real. Run `updpkgsums` once it is; this deliberately fails checksum
|
|
17
|
+
# verification (rather than using SKIP) so makepkg refuses to build silently
|
|
18
|
+
# unverified.
|
|
19
|
+
sha256sums=('0000000000000000000000000000000000000000000000000000000000000000')
|
|
20
|
+
|
|
21
|
+
build() {
|
|
22
|
+
cd "${_pkgsrc}"
|
|
23
|
+
python -m build --wheel --no-isolation
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
check() {
|
|
27
|
+
cd "${_pkgsrc}"
|
|
28
|
+
python -m venv --system-site-packages test-env
|
|
29
|
+
test-env/bin/python -m installer dist/*.whl
|
|
30
|
+
test-env/bin/python -m pytest -o addopts="" tests/
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
package() {
|
|
34
|
+
cd "${_pkgsrc}"
|
|
35
|
+
python -m installer --destdir="${pkgdir}" dist/*.whl
|
|
36
|
+
|
|
37
|
+
install -vDm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
|
|
38
|
+
install -vDm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
|
39
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# cdf
|
|
2
|
+
|
|
3
|
+
`cdf` ("cd" + "find") interactively searches the directory tree below your
|
|
4
|
+
current working directory for a term, and `cd`s into the directory you
|
|
5
|
+
select.
|
|
6
|
+
|
|
7
|
+
- Search scope is always the current working directory downward — never the
|
|
8
|
+
whole filesystem, never a cached/global index.
|
|
9
|
+
- By default it only matches git repository roots and stops descending once
|
|
10
|
+
it finds one (pass `--no-git` to walk every directory instead).
|
|
11
|
+
- Symlinked directories are listed (in git mode, only if they point at a
|
|
12
|
+
repository) but never descended into, so symlink loops can't hang the walk.
|
|
13
|
+
- Selection is powered by [`fzf`](https://github.com/junegunn/fzf) 0.44 or
|
|
14
|
+
newer, which must be on `PATH`. Your `FZF_DEFAULT_OPTS` (theme, layout,
|
|
15
|
+
`--print-query`, `--accept-nth`, ...) are respected.
|
|
16
|
+
- Results are shown (and fuzzy-matched) relative to the search root, so the
|
|
17
|
+
root's own path doesn't match every query.
|
|
18
|
+
- Directory names are shown with terminal control characters (escape
|
|
19
|
+
sequences, newlines, tabs) replaced by `?`, so a hostile name in a cloned
|
|
20
|
+
repo can't mess with your terminal; the directory you pick is still the
|
|
21
|
+
exact one on disk.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
### From source
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
uv sync
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This installs the `cdf` console script into `.venv/bin/`.
|
|
32
|
+
|
|
33
|
+
### Arch Linux
|
|
34
|
+
|
|
35
|
+
A `PKGBUILD` is provided at the repo root, sourcing the tarball from this
|
|
36
|
+
repo's own `v*.*.*` git tags:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
makepkg -si
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`cdf` (the binary) only *resolves* a path and prints it to stdout — a Python
|
|
43
|
+
process can't change its parent shell's working directory. Load the shell
|
|
44
|
+
integration so the `cdf` command actually `cd`s for you:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
# ~/.bashrc
|
|
48
|
+
eval "$(command cdf --init bash)"
|
|
49
|
+
# ~/.zshrc
|
|
50
|
+
eval "$(command cdf --init zsh)"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```fish
|
|
54
|
+
# ~/.config/fish/config.fish
|
|
55
|
+
command cdf --init fish | source
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This defines a shell function also named `cdf`, which takes priority over
|
|
59
|
+
the binary in your shell and calls it internally via `command cdf`. Make
|
|
60
|
+
sure the `cdf` binary itself is on `PATH` (e.g. via `uv tool install .`, or
|
|
61
|
+
by adding `.venv/bin` to `PATH`).
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
cdf [term ...] [-p/--path ROOT] [--git/--no-git] [-a/--all|--no-all] [-d/--max-depth N]
|
|
67
|
+
[-x/--one-file-system|--no-one-file-system] [-c/--config FILE]
|
|
68
|
+
cdf --init {bash,zsh,fish}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Flag | Default | Meaning |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `term ...` | `""` | initial fuzzy filter text passed to `fzf` (several words are joined with spaces) |
|
|
74
|
+
| `-p, --path` | cwd | root to search from |
|
|
75
|
+
| `--git` / `--no-git` | `--git` | only match git repository roots |
|
|
76
|
+
| `-a, --all` / `--no-all` | `--no-all` | include hidden/dot directories |
|
|
77
|
+
| `-d, --max-depth N` | no limit | only look `N` levels below the root (`0` = no limit, to lift a limit set in the config file) |
|
|
78
|
+
| `-x, --one-file-system` / `--no-one-file-system` | off | list mount points but don't descend into them, like `find -xdev` |
|
|
79
|
+
| `-c, --config` | `$CDF_CONFIG_PATH`, else first existing default | config file path |
|
|
80
|
+
| `--init SHELL` | | print the shell integration for `bash`, `zsh` or `fish` and exit |
|
|
81
|
+
|
|
82
|
+
Run `cdf --help` for the full reference, or `cdf --version` (the shell
|
|
83
|
+
function special-cases `--help`/`-h`/`--version`/`--init` to pass them straight
|
|
84
|
+
through instead of attempting to `cd` into their output).
|
|
85
|
+
|
|
86
|
+
### Exit codes
|
|
87
|
+
|
|
88
|
+
- `0` — a directory was selected
|
|
89
|
+
- `1` — nothing was selected (cancelled in `fzf`, or no candidates found, in
|
|
90
|
+
which case a hint is printed on stderr)
|
|
91
|
+
- `2` — an error occurred (message on stderr) — e.g. `fzf` missing, an
|
|
92
|
+
unreadable search root, or a bad config file
|
|
93
|
+
- `130` — interrupted with Ctrl-C
|
|
94
|
+
|
|
95
|
+
## Config file
|
|
96
|
+
|
|
97
|
+
The config file path is resolved in this order:
|
|
98
|
+
|
|
99
|
+
1. `-c/--config FILE` — must exist, or it's an error.
|
|
100
|
+
2. `$CDF_CONFIG_PATH` — must exist, or it's an error.
|
|
101
|
+
3. Otherwise, the first of these that exists (silently skipped if none do):
|
|
102
|
+
`$XDG_CONFIG_HOME/cdf/cdf.conf` (default `~/.config/cdf/cdf.conf`), then
|
|
103
|
+
`~/cdf.conf`.
|
|
104
|
+
|
|
105
|
+
All keys are optional (TOML syntax, despite the `.conf` extension):
|
|
106
|
+
|
|
107
|
+
```toml
|
|
108
|
+
path = "~/projects" # string, expanded with ~; relative paths are relative to this file
|
|
109
|
+
git = true # boolean
|
|
110
|
+
hidden = false # boolean
|
|
111
|
+
ignore = ["node_modules", "__pycache__", ".venv"] # glob patterns of directory names to skip
|
|
112
|
+
max_depth = 0 # non-negative integer, 0 = no limit
|
|
113
|
+
one_file_system = false # boolean
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`ignore` replaces the default list shown above (set `ignore = []` to skip
|
|
117
|
+
nothing). Patterns match one directory name at a time, so they can't contain
|
|
118
|
+
`/`. `.git` directories are never descended into regardless.
|
|
119
|
+
|
|
120
|
+
`one_file_system` is off by default because btrfs subvolumes (and some other
|
|
121
|
+
setups) report their own device IDs, so turning it on would hide those
|
|
122
|
+
directories. Turn it on if you run `cdf --no-git` from places like `/` or `~`
|
|
123
|
+
that have network or FUSE mounts below them, which can be slow or hang.
|
|
124
|
+
|
|
125
|
+
Unknown keys, and values of the wrong type (e.g. `git = "false"` instead of
|
|
126
|
+
`git = false`), are rejected with an error, to catch typos early.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
make run # uv run python -m cdf
|
|
132
|
+
make test # pytest + ruff + ruff-format + mypy (strict) + coverage, gated at 100%
|
|
133
|
+
make lint # uv run ruff check .
|
|
134
|
+
make build # sdist + wheel, with the build backend pinned by hash (build-constraints.txt)
|
|
135
|
+
make build-constraints # re-lock build-constraints.txt after editing build-constraints.in
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Supported Python: 3.11 through 3.14; CI runs the full `make test` gate on
|
|
139
|
+
each, with the bash/zsh/fish wrapper tests running against pinned zsh 5.9
|
|
140
|
+
and fish 4.9.3.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hatchling==1.32.4
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
hatchling==1.32.4 \
|
|
2
|
+
--hash=sha256:08ecf7548fb48205e7f213d70c71e67b8271b7242093dc3f1da578b42c734a2c \
|
|
3
|
+
--hash=sha256:c4468f73144c054d2aab4ef0f0378c43b9878bf07f8ffd6b79690e970d375f07
|
|
4
|
+
# via -r build-constraints.in
|
|
5
|
+
packaging==26.3 \
|
|
6
|
+
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
|
|
7
|
+
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
|
|
8
|
+
# via hatchling
|
|
9
|
+
pathspec==1.1.1 \
|
|
10
|
+
--hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \
|
|
11
|
+
--hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189
|
|
12
|
+
# via hatchling
|
|
13
|
+
pluggy==1.6.0 \
|
|
14
|
+
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
|
|
15
|
+
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
|
|
16
|
+
# via hatchling
|
|
17
|
+
tomlkit==0.15.1 \
|
|
18
|
+
--hash=sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304 \
|
|
19
|
+
--hash=sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97
|
|
20
|
+
# via hatchling
|
|
21
|
+
trove-classifiers==2026.9.21.13 \
|
|
22
|
+
--hash=sha256:0a9ebc8d4e2f3e8a22848c5258033035bec17a3012ac3fea16dbaa764489eb71 \
|
|
23
|
+
--hash=sha256:8b1ff4f9c191b1040b71c37f1e445ab99732911e3cd91de52838453a854d7a17
|
|
24
|
+
# via hatchling
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cdf-shell"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Interactive git-project cd/find"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Felipe Ruhland", email = "pypi@feliperuhland.com" }]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
cdf = "cdf.cli:main"
|
|
14
|
+
|
|
15
|
+
[dependency-groups]
|
|
16
|
+
dev = [
|
|
17
|
+
"pytest>=8.0",
|
|
18
|
+
"pytest-cov>=5.0",
|
|
19
|
+
"pytest-ruff>=0.4",
|
|
20
|
+
"pytest-mypy>=0.10",
|
|
21
|
+
"ruff>=0.6",
|
|
22
|
+
"mypy>=1.11",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.pytest.ini_options]
|
|
26
|
+
addopts = "--ruff --ruff-format --mypy --cov=cdf --cov-report=term-missing --cov-fail-under=100"
|
|
27
|
+
testpaths = ["src", "tests"]
|
|
28
|
+
|
|
29
|
+
[tool.coverage.run]
|
|
30
|
+
source = ["src/cdf"]
|
|
31
|
+
omit = ["src/cdf/__main__.py"]
|
|
32
|
+
|
|
33
|
+
[tool.coverage.report]
|
|
34
|
+
exclude_also = [
|
|
35
|
+
"if __name__ == .__main__.:",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[tool.ruff]
|
|
39
|
+
line-length = 100
|
|
40
|
+
target-version = "py311"
|
|
41
|
+
|
|
42
|
+
[tool.ruff.lint]
|
|
43
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
44
|
+
|
|
45
|
+
[tool.mypy]
|
|
46
|
+
strict = true
|
|
47
|
+
# Check against the oldest supported Python, not whichever one runs mypy.
|
|
48
|
+
python_version = "3.11"
|
|
49
|
+
files = ["src", "tests"]
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
# Lower bound only (PEP 639 license fields need 1.27+) so distro builds can use
|
|
53
|
+
# their own hatchling; CI and `make build` pin it exactly via build-constraints.txt.
|
|
54
|
+
requires = ["hatchling>=1.27"]
|
|
55
|
+
build-backend = "hatchling.build"
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel]
|
|
58
|
+
packages = ["src/cdf"]
|
|
File without changes
|