exist-shell 0.1.1__tar.gz → 0.2.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.
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.github/workflows/docs.yml +2 -2
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.github/workflows/e2e.yml +4 -4
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.github/workflows/release.yml +2 -2
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.github/workflows/ruff.yml +3 -3
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.github/workflows/tests.yml +3 -3
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.github/workflows/ty.yml +4 -4
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.gitignore +2 -0
- exist_shell-0.2.0/CHANGELOG.md +83 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/Makefile +2 -2
- {exist_shell-0.1.1 → exist_shell-0.2.0}/PKG-INFO +60 -3
- {exist_shell-0.1.1 → exist_shell-0.2.0}/README.md +59 -2
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/api.md +8 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/commands.md +77 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/index.md +1 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/installation.md +24 -2
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/sync.md +25 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/pyproject.toml +5 -5
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T05_put.sh +7 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T07_cat.sh +9 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T08_cp.sh +35 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T09_rm.sh +23 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T12_sync.sh +100 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T13_mv.sh +37 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T14_exec.sh +43 -0
- exist_shell-0.2.0/scripts/e2e/sections/T19_find.sh +68 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e.sh +2 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/cache.py +95 -12
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/client/__init__.py +7 -3
- exist_shell-0.2.0/src/exist_shell/client/_base.py +102 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/client/_collections.py +62 -13
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/client/_documents.py +64 -23
- exist_shell-0.2.0/src/exist_shell/client/_queries.py +105 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/cat.py +1 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/chmod.py +15 -20
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/chown.py +15 -20
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/cp.py +5 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/exec.py +45 -8
- exist_shell-0.2.0/src/exist_shell/commands/find.py +64 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/mv.py +7 -0
- exist_shell-0.2.0/src/exist_shell/commands/rm.py +47 -0
- exist_shell-0.2.0/src/exist_shell/commands/sync.py +1405 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/completions.py +126 -12
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/config.py +34 -2
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/exceptions.py +28 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/main.py +20 -1
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/utils.py +44 -7
- exist_shell-0.2.0/tests/__init__.py +1 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/conftest.py +8 -0
- exist_shell-0.2.0/tests/test_cache.py +335 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_client.py +584 -5
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_cat.py +19 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_chmod.py +38 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_chown.py +27 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_collection.py +36 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_cp.py +34 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_edit.py +14 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_exec.py +101 -1
- exist_shell-0.2.0/tests/test_commands_find.py +146 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_group.py +37 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_ls.py +17 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_mkdir.py +7 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_mv.py +51 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_put.py +17 -0
- exist_shell-0.2.0/tests/test_commands_rm.py +129 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_server.py +35 -0
- exist_shell-0.2.0/tests/test_commands_sync.py +1791 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_commands_user.py +68 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_completions.py +154 -17
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_config.py +104 -6
- exist_shell-0.2.0/tests/test_exceptions.py +55 -0
- exist_shell-0.2.0/tests/test_main.py +45 -0
- exist_shell-0.2.0/tests/test_utils.py +258 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/tests/test_xquery.py +32 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/uv.lock +48 -48
- exist_shell-0.1.1/CHANGELOG.md +0 -52
- exist_shell-0.1.1/src/exist_shell/client/_base.py +0 -65
- exist_shell-0.1.1/src/exist_shell/client/_queries.py +0 -37
- exist_shell-0.1.1/src/exist_shell/commands/rm.py +0 -23
- exist_shell-0.1.1/src/exist_shell/commands/sync.py +0 -767
- exist_shell-0.1.1/tests/__init__.py +0 -0
- exist_shell-0.1.1/tests/test_cache.py +0 -181
- exist_shell-0.1.1/tests/test_commands_rm.py +0 -66
- exist_shell-0.1.1/tests/test_commands_sync.py +0 -636
- exist_shell-0.1.1/tests/test_utils.py +0 -141
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.gitguardian.yaml +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/.github/dependabot.yml +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/CLAUDE.md +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/LICENSE +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/completion.md +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/configuration.md +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/docs/development.md +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/mkdocs.yml +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/docker.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/lib.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T02_server.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T03_collection.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T04_ls.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T06_ls_after.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T10_mkdir.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T11_edit.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T15_user.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T16_group.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T17_chown.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/scripts/e2e/sections/T18_chmod.sh +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/__init__.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/client/_groups.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/client/_permissions.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/client/_users.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/__init__.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/collection.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/edit.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/group.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/ls.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/mkdir.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/put.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/server.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/commands/user.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/models.py +0 -0
- {exist_shell-0.1.1 → exist_shell-0.2.0}/src/exist_shell/xquery.py +0 -0
|
@@ -16,10 +16,10 @@ jobs:
|
|
|
16
16
|
deploy:
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
20
20
|
|
|
21
21
|
- name: Set up Python
|
|
22
|
-
uses: actions/setup-python@
|
|
22
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
23
23
|
with:
|
|
24
24
|
python-version: "3.13"
|
|
25
25
|
|
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
outputs:
|
|
15
15
|
flags: ${{ steps.list.outputs.flags }}
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
17
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
18
18
|
- name: Build image matrix from --list-images
|
|
19
19
|
id: list
|
|
20
20
|
run: |
|
|
@@ -31,9 +31,9 @@ jobs:
|
|
|
31
31
|
flag: ${{ fromJson(needs.matrix.outputs.flags) }}
|
|
32
32
|
fail-fast: false
|
|
33
33
|
steps:
|
|
34
|
-
- uses: actions/checkout@
|
|
34
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
35
35
|
- run: sudo apt-get install -y libxml2-utils
|
|
36
|
-
- uses: astral-sh/setup-uv@
|
|
36
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
37
37
|
with:
|
|
38
38
|
enable-cache: true
|
|
39
39
|
- run: uv sync
|
|
@@ -88,7 +88,7 @@ jobs:
|
|
|
88
88
|
done
|
|
89
89
|
} > comment.md
|
|
90
90
|
cat comment.md
|
|
91
|
-
- uses: marocchino/sticky-pull-request-comment@
|
|
91
|
+
- uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v2
|
|
92
92
|
with:
|
|
93
93
|
header: e2e
|
|
94
94
|
path: comment.md
|
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
if: github.actor == 'ambs'
|
|
16
16
|
environment: release
|
|
17
17
|
steps:
|
|
18
|
-
- uses: actions/checkout@
|
|
18
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
19
19
|
|
|
20
20
|
- name: Verify signed tag
|
|
21
21
|
run: |
|
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
env:
|
|
25
25
|
GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
|
|
26
26
|
|
|
27
|
-
- uses: astral-sh/setup-uv@
|
|
27
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
28
28
|
with:
|
|
29
29
|
enable-cache: true
|
|
30
30
|
|
|
@@ -12,8 +12,8 @@ jobs:
|
|
|
12
12
|
ruff:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: astral-sh/setup-uv@
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
16
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
17
17
|
with:
|
|
18
18
|
enable-cache: true
|
|
19
19
|
- run: uv sync
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
echo '```'
|
|
38
38
|
} > comment.md
|
|
39
39
|
fi
|
|
40
|
-
- uses: marocchino/sticky-pull-request-comment@
|
|
40
|
+
- uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v2
|
|
41
41
|
if: github.event_name == 'pull_request'
|
|
42
42
|
with:
|
|
43
43
|
header: ruff
|
|
@@ -12,8 +12,8 @@ jobs:
|
|
|
12
12
|
test:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: astral-sh/setup-uv@
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
16
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
17
17
|
with:
|
|
18
18
|
enable-cache: true
|
|
19
19
|
- run: uv sync
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
echo "**${test_line}** "
|
|
35
35
|
echo "**Coverage: ${coverage}**"
|
|
36
36
|
} > comment.md
|
|
37
|
-
- uses: marocchino/sticky-pull-request-comment@
|
|
37
|
+
- uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v2
|
|
38
38
|
if: github.event_name == 'pull_request'
|
|
39
39
|
with:
|
|
40
40
|
header: tests
|
|
@@ -12,15 +12,15 @@ jobs:
|
|
|
12
12
|
typecheck:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: astral-sh/setup-uv@
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
16
|
+
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
17
17
|
with:
|
|
18
18
|
enable-cache: true
|
|
19
19
|
- run: uv sync
|
|
20
20
|
- name: Run ty
|
|
21
21
|
id: ty
|
|
22
22
|
continue-on-error: true
|
|
23
|
-
run: uv run ty check src/ 2>&1 | tee ty.log
|
|
23
|
+
run: uv run ty check src/ tests/ 2>&1 | tee ty.log
|
|
24
24
|
- name: Build PR comment
|
|
25
25
|
if: github.event_name == 'pull_request'
|
|
26
26
|
run: |
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
echo '```'
|
|
38
38
|
} > comment.md
|
|
39
39
|
fi
|
|
40
|
-
- uses: marocchino/sticky-pull-request-comment@
|
|
40
|
+
- uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v2
|
|
41
41
|
if: github.event_name == 'pull_request'
|
|
42
42
|
with:
|
|
43
43
|
header: ty
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 - 2026-07-28
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- `config.toml` (which stores server passwords in plaintext) was written with default umask permissions — typically world-readable on multi-user machines. It is now written `0600` with the config directory at `0700`, and `exsh` warns on stderr when an existing config file is group/world-readable
|
|
8
|
+
- Non-401/404 HTTP errors (most notably `403 Forbidden`) leaked a raw `httpx` traceback; they now exit cleanly with a one-line error message. `cp` local→remote also gained the XML well-formedness check that `put` and `sync` already had
|
|
9
|
+
- The sync manifest was keyed only by the remote path, so syncing the same remote collection against two different local directories shared one manifest and silently corrupted each other's per-file state (false conflicts, wrong skip decisions). The manifest key now includes the resolved local directory; existing manifests are re-keyed via a one-time full recheck on the next sync
|
|
10
|
+
- A single failed transfer during sync left the rest of the queue finishing silently in the background with no progress output, ending in a generic unattributed error. Failures are now reported per file (`! <path> (error: ...)`) as they happen, the remaining queue keeps draining, the summary shows a `failed` count, and sync exits non-zero when anything failed
|
|
11
|
+
- `is_remote()` classified any argument containing `:` as a `nick:path` remote target, so Windows paths (`C:\data\doc.xml`) and any local path with a colon in a directory component were always misparsed as a remote nick. A prefix is now only treated as a nick when it matches a configured collection, isn't a single-letter drive prefix (`C:\`, `C:/`), and doesn't itself contain a path separator. Server/collection nicks must now be at least 2 characters, guaranteeing a configured nick can never collide with a Windows drive letter
|
|
12
|
+
- `mv` of a collection onto itself (`exsh mv nick:/a nick:/a`) or into itself via a trailing slash (`exsh mv nick:/a nick:/a/`) copied the contents onto/into the source and then recursively deleted it, destroying the data. `mv` now aborts with an error when the target is the same as, or nested inside, the source, for both collections and single documents
|
|
13
|
+
- `cat`/`cp`/`mv`/`edit`/`sync` downloading an executable resource (`.xql`, `.xqm`) ran the query on the server and returned its result instead of the document's raw source
|
|
14
|
+
- The above fix relied on eXist's `_source=yes` REST parameter, which requires the path to be explicitly allowlisted in the server's `descriptor.xml` — not the default for anything under `/db`, regardless of the caller's own read permission. This returned `403 Forbidden` for every executable-resource download (`.xq`, `.xql`, `.xqm`, `.xquery`, `.xqy`, `.xqws`) on an unconfigured (i.e. most) server. These resources are now fetched via `util:binary-doc()`, which only needs the read + query-eval permission already required to execute them
|
|
15
|
+
- `rm <nick>:<path>` on a path that is a collection silently deleted the entire subtree with no confirmation, since eXist's `DELETE` is recursive on collections. `rm` now refuses a collection target unless `--recursive`/`-r` is given, and prompts for confirmation unless `--yes`/`-y` is also passed
|
|
16
|
+
- `create_collection`, `is_collection`, and `move_document` embedded paths/names into generated XQuery without escaping `"`/`&`, unlike every other client method — a path or name containing either broke the query (or, in `move_document`, changed its meaning)
|
|
17
|
+
- `rm` and `find --remove` never invalidated the completion cache after deleting, so deleted documents kept appearing in tab completion until the cache TTL expired
|
|
18
|
+
|
|
19
|
+
### Enhancements
|
|
20
|
+
|
|
21
|
+
- **Independent client timeouts**: connect (10s), read (30s), and write (10s) now have separate budgets instead of one flat 30s timeout for every phase, and timeout errors say which phase failed — "Cannot connect to ..." for handshake failures vs "Server at ... did not respond in time" for read/write timeouts.
|
|
22
|
+
- **Faster, correct bash tab-completion for `nick:path` targets**: the generated bash completion script now handles the `:` word-break correctly (no more `dlp:dlp:...` insertions), completing a collection no longer needs an extra backspace before continuing into it, and completion listings are filtered and capped server-side and cached per prefix so progressive typing reuses a single fetch. Re-run `exsh --install-completion bash` after upgrading to pick up the new script.
|
|
23
|
+
- **Exit code 130 on Ctrl+C** for the whole CLI (previously sync-only), including during shell completion itself, which Typer/click don't handle on their own.
|
|
24
|
+
- **Parallel sync (`--jobs N`)**: uploads, downloads, and remote directory listings now run concurrently. The `--jobs N` flag (default: 4) controls the number of parallel workers. Use `--jobs 1` to restore fully sequential behaviour.
|
|
25
|
+
- **Clean Ctrl+C handling**: interrupting a sync now exits with code 130, saves the manifest (so the next run can resume), and prints a single `Interrupted.` message instead of a Python traceback.
|
|
26
|
+
- **`exsh find`**: locate documents by XPath expression anywhere in their content (`exsh find <nick>[:<path>] --query 'foo[@type="draft"]'`), with `--remove`/`--yes` to delete every match in one step.
|
|
27
|
+
- **`exsh exec --resource <nick>:<path.xql>`**: execute a stored `.xql`/`.xqm` resource in place (a plain `GET`, matching how eXist runs these resources natively) instead of downloading it and re-running it locally. Repeat `-p/--param NAME=VALUE` to forward query-string parameters as external variables.
|
|
28
|
+
- **`exsh sync --exclude`**: repeatable glob patterns to skip paths on both sides of a sync (`--exclude '*.tmp' --exclude build`). A pattern with `/` matches that relative path and its subtree; one without `/` matches any path segment at any depth. Patterns persist in the sync manifest per (server, remote path, local folder), so later runs keep excluding without the flag; new patterns merge into the stored list and `--clear-exclude` resets it. Excluded paths are never transferred and never deleted by `--delete`. Files synced before becoming excluded are deleted on both sides after confirmation (`--yes` skips the prompt, declining keeps the files, `--keep-excluded` keeps them without asking); either way they stop being tracked.
|
|
29
|
+
|
|
30
|
+
## 0.1.2 - 2026-06-23
|
|
31
|
+
|
|
32
|
+
### Documentation
|
|
33
|
+
|
|
34
|
+
- Document installing `exsh` from PyPI (`uv tool install`, `pipx install`, `uvx --from`); the existing `git+https://...` instructions are kept as an alternative for tracking unreleased commits
|
|
35
|
+
|
|
36
|
+
## 0.1.1 - 2026-06-23
|
|
37
|
+
|
|
38
|
+
### Fixes
|
|
39
|
+
|
|
40
|
+
- Complete PyPI project metadata: `readme`, `license` (MIT), `authors`, `classifiers`, `project.urls`, and `keywords` were all missing, leaving the 0.1.0 PyPI listing with no description, license, or links
|
|
41
|
+
- Add `LICENSE` file (MIT)
|
|
42
|
+
|
|
43
|
+
## 0.1.0 - 2026-06-23
|
|
44
|
+
|
|
45
|
+
### Commands
|
|
46
|
+
|
|
47
|
+
| Command | Description |
|
|
48
|
+
|---------|-------------|
|
|
49
|
+
| `server add / ls / rm` | Register, list, and remove eXist-db server configurations; `rm` cascade-removes associated collections |
|
|
50
|
+
| `server rename <old> <new>` | Rename a server nick; all registered collections are updated automatically |
|
|
51
|
+
| `collection add / new / ls / rm` | Manage named collection shortcuts; `new` creates the collection on the server and registers it in one step (idempotent: no-op if already exists) |
|
|
52
|
+
| `ls [path]` | List documents and sub-collections at a remote path; `--sort name\|time`, `--reverse`, `--names-only` |
|
|
53
|
+
| `cat <path>` | Print a remote document to stdout; `--raw` skips binary detection |
|
|
54
|
+
| `put <path>` | Upload a document from stdin or a local file; auto-detects MIME type |
|
|
55
|
+
| `cp <src> <dst>` | Copy documents local↔remote or remote↔remote |
|
|
56
|
+
| `rm <path...>` | Delete one or more remote documents |
|
|
57
|
+
| `mkdir <path>` | Create a remote collection (idempotent) |
|
|
58
|
+
| `edit <path>` | Download, open in `$VISUAL`/`$EDITOR`, re-upload if changed |
|
|
59
|
+
| `sync push/pull <dir> <path>` | Sync a local directory tree with a remote collection; supports `--delete`, `--dry-run`, `--force` |
|
|
60
|
+
| `mv <src> <dst>` | Move or rename a document or collection; trailing `/` on dest moves into that collection; cross-server moves not supported |
|
|
61
|
+
| `exec <nick>[:<path>]` | Read an XQuery script from a file or stdin, optionally preprocess it (version declaration, functx import), validate locally with BaseX or Saxon (auto-detected via PATH), then execute against eXist and print the result; supports `--no-fix`, `--no-validate`, `--validator`, `--list-validators` |
|
|
62
|
+
| `user ls` | List user accounts and their groups; accepts `@server` positional |
|
|
63
|
+
| `user add <user[@server]>` | Create a user account (prompts for password) |
|
|
64
|
+
| `user rm <user[@server]>` | Remove a user account |
|
|
65
|
+
| `user info <user[@server]>` | Show user account details |
|
|
66
|
+
| `user passwd <user[@server]>` | Change a user's password; `--stdin` for scripting |
|
|
67
|
+
| `group ls` | List groups and their members; accepts `@server` positional |
|
|
68
|
+
| `group add <group[@server]>` | Create a group |
|
|
69
|
+
| `group rm <group[@server]>` | Remove a group |
|
|
70
|
+
| `chown <spec> <path>` | Change owner and/or group of a document or collection; `--recursive / -R` for collections; spec forms: `owner`, `:group`, `owner:group` |
|
|
71
|
+
| `chmod <mode> <path>` | Change POSIX permissions of a document or collection; `--recursive / -R` for collections; accepts octal (`0755`) or symbolic (`u+x`, `go-w`, `a=rw`) modes |
|
|
72
|
+
|
|
73
|
+
### Infrastructure
|
|
74
|
+
|
|
75
|
+
- Shell completion for collection/document paths (with TTL cache)
|
|
76
|
+
- `<name>@<server>` syntax for ad-hoc server targeting in `collection add`, `collection new`, `user *`, and `group *`
|
|
77
|
+
- `--server` option on all commands completes registered server nicks
|
|
78
|
+
- Shell completion for `<name>@<server>` and `@<server>` argument forms
|
|
79
|
+
- Path traversal validation and URL encoding
|
|
80
|
+
- Google-style docstrings enforced via ruff
|
|
81
|
+
- CI: pytest + coverage (Codecov), ruff, ty, e2e test suite against live eXist-db in Docker
|
|
82
|
+
- CI: sticky PR comments for tests (counts + coverage), ruff, ty, and e2e workflows
|
|
83
|
+
- CI: e2e workflow with dynamic image matrix from `--list-images`; `fail-fast: false`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exist-shell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Command-line tool to interact with eXist-db via REST
|
|
5
5
|
Project-URL: Homepage, https://github.com/ambs/exist-shell
|
|
6
6
|
Project-URL: Repository, https://github.com/ambs/exist-shell
|
|
@@ -50,7 +50,13 @@ A command-line tool to interact with an [eXist-db](https://exist-db.org) server
|
|
|
50
50
|
Install system-wide with `uv tool`:
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
uv tool install
|
|
53
|
+
uv tool install exist-shell
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or with `pipx`:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pipx install exist-shell
|
|
54
60
|
```
|
|
55
61
|
|
|
56
62
|
This places `exsh` on your `PATH`. Verify with:
|
|
@@ -65,6 +71,22 @@ To uninstall:
|
|
|
65
71
|
uv tool uninstall exist-shell
|
|
66
72
|
```
|
|
67
73
|
|
|
74
|
+
### Run without installing
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uvx --from exist-shell exsh --version
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`uvx` fetches the package into a temporary, cached environment and runs it — handy for one-off use or trying out a new release.
|
|
81
|
+
|
|
82
|
+
### Install from git
|
|
83
|
+
|
|
84
|
+
To track an unreleased commit instead of a PyPI release:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv tool install git+https://github.com/ambs/exist-shell
|
|
88
|
+
```
|
|
89
|
+
|
|
68
90
|
## Configuration
|
|
69
91
|
|
|
70
92
|
### Add a server
|
|
@@ -113,11 +135,13 @@ Configuration is stored at `~/.config/exsh/config.toml`.
|
|
|
113
135
|
| `exsh put <file> <nick>:<path>` | Upload a local file to a collection |
|
|
114
136
|
| `exsh cp <src> <dst>` | Copy a document (local ↔ remote or remote ↔ remote) |
|
|
115
137
|
| `exsh edit <nick>:<path>` | Open a document in `$EDITOR`, re-upload if changed |
|
|
116
|
-
| `exsh rm <nick>:<path>...` | Delete one or more documents |
|
|
138
|
+
| `exsh rm <nick>:<path>...` | Delete one or more documents; `--recursive`/`-r` (with `--yes`/`-y`) to delete a collection |
|
|
117
139
|
| `exsh mkdir <nick>:<path>` | Create a collection |
|
|
118
140
|
| `exsh sync <local> <nick>[:<path>]` | Push a local folder to a remote collection |
|
|
119
141
|
| `exsh sync <nick>[:<path>] <local>` | Pull a remote collection to a local folder |
|
|
120
142
|
| `exsh exec <nick>[:<path>]` | Execute an XQuery script on a server |
|
|
143
|
+
| `exsh exec --resource <nick>:<path.xql>` | Execute a stored resource in place, forwarding `-p name=value` params |
|
|
144
|
+
| `exsh find <nick>[:<path>] --query <xpath>` | Find documents matching an XPath expression, with optional `--remove` |
|
|
121
145
|
| `exsh server add <host>` | Register a server |
|
|
122
146
|
| `exsh server ls` | List registered servers |
|
|
123
147
|
| `exsh server rm <nick>` | Remove a server (and its collections) |
|
|
@@ -163,6 +187,9 @@ exsh rm mydata:reports/2025/old.xml
|
|
|
163
187
|
# Delete multiple documents
|
|
164
188
|
exsh rm mydata:reports/2025/a.xml mydata:reports/2025/b.xml
|
|
165
189
|
|
|
190
|
+
# Delete a whole collection (prompts unless --yes is also given)
|
|
191
|
+
exsh rm --recursive mydata:reports/2025
|
|
192
|
+
|
|
166
193
|
# Create a subcollection
|
|
167
194
|
exsh mkdir mydata:reports/2026
|
|
168
195
|
|
|
@@ -178,6 +205,18 @@ exsh exec mydata:/ --no-fix -f query.xq
|
|
|
178
205
|
# List locally available XQuery validators
|
|
179
206
|
exsh exec --list-validators
|
|
180
207
|
|
|
208
|
+
# Execute a stored .xql/.xqm resource in place, rather than downloading it first
|
|
209
|
+
exsh exec --resource mydata:/report.xql
|
|
210
|
+
|
|
211
|
+
# ...forwarding query-string parameters as external variables
|
|
212
|
+
exsh exec --resource mydata:/report.xql -p from=2026-01-01 -p to=2026-12-31
|
|
213
|
+
|
|
214
|
+
# List documents containing a matching element anywhere in their tree
|
|
215
|
+
exsh find mydata:reports --query 'foo[@type="draft"]'
|
|
216
|
+
|
|
217
|
+
# Delete every match, skipping the confirmation prompt
|
|
218
|
+
exsh find mydata:reports --query 'foo[@type="draft"]' --remove --yes
|
|
219
|
+
|
|
181
220
|
# Push a local folder to the server (only transfers changed files)
|
|
182
221
|
exsh sync ./reports mydata:reports
|
|
183
222
|
|
|
@@ -190,6 +229,9 @@ exsh sync --dry-run ./reports mydata:reports
|
|
|
190
229
|
# Push and remove files on the server that no longer exist locally
|
|
191
230
|
exsh sync --delete ./reports mydata:reports
|
|
192
231
|
|
|
232
|
+
# Sync everything except temp files and the build folder (remembered for later runs)
|
|
233
|
+
exsh sync --exclude '*.tmp' --exclude build ./reports mydata:reports
|
|
234
|
+
|
|
193
235
|
# Change owner and group of a document
|
|
194
236
|
exsh chown alice:editors mydata:reports/annual.xml
|
|
195
237
|
|
|
@@ -216,13 +258,26 @@ exsh chmod -R 0644 mydata:data
|
|
|
216
258
|
|
|
217
259
|
**Conflicts** (both sides changed since last sync) are reported and skipped — use `--force` to override.
|
|
218
260
|
|
|
261
|
+
**Excludes:** `--exclude`/`-e <pattern>` (repeatable) skips matching paths on both sides of the sync — they are neither transferred nor ever deleted by `--delete`. Patterns are `fnmatch`-style (`*`, `?`, `[seq]`), matched against the path relative to the sync root:
|
|
262
|
+
|
|
263
|
+
- A pattern containing `/` matches that relative path and everything below it (`build/sub` also excludes `build/sub/x.xml`). Note that `*` in such a pattern also crosses `/` (plain `fnmatch`, unlike gitignore): `build/*.xml` matches `build/sub/x.xml` too.
|
|
264
|
+
- A pattern without `/` matches any single path segment at any depth — `build` excludes `build/`, `a/build/`, and their contents; `*.tmp` excludes temp files everywhere.
|
|
265
|
+
|
|
266
|
+
Patterns are persisted in the sync manifest, per (server, remote path, local folder), so later runs keep excluding without repeating the flag. New `--exclude` patterns are merged into the stored list; `--clear-exclude` wipes it (combine both to replace the list in one run). Files that were synced before becoming excluded are deleted **on both sides** after a confirmation prompt — `--yes` skips the prompt, declining (or running non-interactively without `--yes`) keeps the files, and `--keep-excluded` keeps them without asking. In every case they stop being tracked.
|
|
267
|
+
|
|
219
268
|
**Options:**
|
|
220
269
|
|
|
221
270
|
| Flag | Effect |
|
|
222
271
|
|------|--------|
|
|
223
272
|
| `--force` / `-f` | Transfer all files, bypassing change detection |
|
|
224
273
|
| `--dry-run` / `-n` | Show what would happen without transferring |
|
|
274
|
+
| `--fail-fast` | Stop on the first conflict, invalid XML, or transfer error (runs sequentially; the manifest is saved so the run can resume) |
|
|
275
|
+
| `--jobs N` / `-j` | Number of parallel transfer workers (default: 4); `--jobs 1` for fully sequential behaviour |
|
|
225
276
|
| `--delete` | Remove files and empty folders on the destination that no longer exist on the source |
|
|
277
|
+
| `--exclude PATTERN` / `-e` | Skip matching paths on both sides (repeatable); merged into the list stored for this sync pair |
|
|
278
|
+
| `--clear-exclude` | Wipe the stored exclude list before applying any `--exclude` given in the same run |
|
|
279
|
+
| `--keep-excluded` | Keep previously synced copies of newly excluded paths; only stop tracking them |
|
|
280
|
+
| `--yes` / `-y` | Skip the confirmation prompt when deleting previously synced, newly excluded files |
|
|
226
281
|
| `--verbose` / `-v` | Also print unchanged (skipped) files |
|
|
227
282
|
| `--checkpoint-every N` | Flush the manifest every N files (default: 100); allows interrupted syncs to resume near the point of failure |
|
|
228
283
|
|
|
@@ -241,6 +296,8 @@ exsh --install-completion zsh
|
|
|
241
296
|
exsh --install-completion fish
|
|
242
297
|
```
|
|
243
298
|
|
|
299
|
+
Upgrading? Re-run `exsh --install-completion bash` to pick up fixes to the generated script — it's written once to `~/.bash_completions/` (or equivalent) and isn't updated automatically.
|
|
300
|
+
|
|
244
301
|
## Development
|
|
245
302
|
|
|
246
303
|
```bash
|
|
@@ -19,7 +19,13 @@ A command-line tool to interact with an [eXist-db](https://exist-db.org) server
|
|
|
19
19
|
Install system-wide with `uv tool`:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
uv tool install
|
|
22
|
+
uv tool install exist-shell
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or with `pipx`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pipx install exist-shell
|
|
23
29
|
```
|
|
24
30
|
|
|
25
31
|
This places `exsh` on your `PATH`. Verify with:
|
|
@@ -34,6 +40,22 @@ To uninstall:
|
|
|
34
40
|
uv tool uninstall exist-shell
|
|
35
41
|
```
|
|
36
42
|
|
|
43
|
+
### Run without installing
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uvx --from exist-shell exsh --version
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`uvx` fetches the package into a temporary, cached environment and runs it — handy for one-off use or trying out a new release.
|
|
50
|
+
|
|
51
|
+
### Install from git
|
|
52
|
+
|
|
53
|
+
To track an unreleased commit instead of a PyPI release:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv tool install git+https://github.com/ambs/exist-shell
|
|
57
|
+
```
|
|
58
|
+
|
|
37
59
|
## Configuration
|
|
38
60
|
|
|
39
61
|
### Add a server
|
|
@@ -82,11 +104,13 @@ Configuration is stored at `~/.config/exsh/config.toml`.
|
|
|
82
104
|
| `exsh put <file> <nick>:<path>` | Upload a local file to a collection |
|
|
83
105
|
| `exsh cp <src> <dst>` | Copy a document (local ↔ remote or remote ↔ remote) |
|
|
84
106
|
| `exsh edit <nick>:<path>` | Open a document in `$EDITOR`, re-upload if changed |
|
|
85
|
-
| `exsh rm <nick>:<path>...` | Delete one or more documents |
|
|
107
|
+
| `exsh rm <nick>:<path>...` | Delete one or more documents; `--recursive`/`-r` (with `--yes`/`-y`) to delete a collection |
|
|
86
108
|
| `exsh mkdir <nick>:<path>` | Create a collection |
|
|
87
109
|
| `exsh sync <local> <nick>[:<path>]` | Push a local folder to a remote collection |
|
|
88
110
|
| `exsh sync <nick>[:<path>] <local>` | Pull a remote collection to a local folder |
|
|
89
111
|
| `exsh exec <nick>[:<path>]` | Execute an XQuery script on a server |
|
|
112
|
+
| `exsh exec --resource <nick>:<path.xql>` | Execute a stored resource in place, forwarding `-p name=value` params |
|
|
113
|
+
| `exsh find <nick>[:<path>] --query <xpath>` | Find documents matching an XPath expression, with optional `--remove` |
|
|
90
114
|
| `exsh server add <host>` | Register a server |
|
|
91
115
|
| `exsh server ls` | List registered servers |
|
|
92
116
|
| `exsh server rm <nick>` | Remove a server (and its collections) |
|
|
@@ -132,6 +156,9 @@ exsh rm mydata:reports/2025/old.xml
|
|
|
132
156
|
# Delete multiple documents
|
|
133
157
|
exsh rm mydata:reports/2025/a.xml mydata:reports/2025/b.xml
|
|
134
158
|
|
|
159
|
+
# Delete a whole collection (prompts unless --yes is also given)
|
|
160
|
+
exsh rm --recursive mydata:reports/2025
|
|
161
|
+
|
|
135
162
|
# Create a subcollection
|
|
136
163
|
exsh mkdir mydata:reports/2026
|
|
137
164
|
|
|
@@ -147,6 +174,18 @@ exsh exec mydata:/ --no-fix -f query.xq
|
|
|
147
174
|
# List locally available XQuery validators
|
|
148
175
|
exsh exec --list-validators
|
|
149
176
|
|
|
177
|
+
# Execute a stored .xql/.xqm resource in place, rather than downloading it first
|
|
178
|
+
exsh exec --resource mydata:/report.xql
|
|
179
|
+
|
|
180
|
+
# ...forwarding query-string parameters as external variables
|
|
181
|
+
exsh exec --resource mydata:/report.xql -p from=2026-01-01 -p to=2026-12-31
|
|
182
|
+
|
|
183
|
+
# List documents containing a matching element anywhere in their tree
|
|
184
|
+
exsh find mydata:reports --query 'foo[@type="draft"]'
|
|
185
|
+
|
|
186
|
+
# Delete every match, skipping the confirmation prompt
|
|
187
|
+
exsh find mydata:reports --query 'foo[@type="draft"]' --remove --yes
|
|
188
|
+
|
|
150
189
|
# Push a local folder to the server (only transfers changed files)
|
|
151
190
|
exsh sync ./reports mydata:reports
|
|
152
191
|
|
|
@@ -159,6 +198,9 @@ exsh sync --dry-run ./reports mydata:reports
|
|
|
159
198
|
# Push and remove files on the server that no longer exist locally
|
|
160
199
|
exsh sync --delete ./reports mydata:reports
|
|
161
200
|
|
|
201
|
+
# Sync everything except temp files and the build folder (remembered for later runs)
|
|
202
|
+
exsh sync --exclude '*.tmp' --exclude build ./reports mydata:reports
|
|
203
|
+
|
|
162
204
|
# Change owner and group of a document
|
|
163
205
|
exsh chown alice:editors mydata:reports/annual.xml
|
|
164
206
|
|
|
@@ -185,13 +227,26 @@ exsh chmod -R 0644 mydata:data
|
|
|
185
227
|
|
|
186
228
|
**Conflicts** (both sides changed since last sync) are reported and skipped — use `--force` to override.
|
|
187
229
|
|
|
230
|
+
**Excludes:** `--exclude`/`-e <pattern>` (repeatable) skips matching paths on both sides of the sync — they are neither transferred nor ever deleted by `--delete`. Patterns are `fnmatch`-style (`*`, `?`, `[seq]`), matched against the path relative to the sync root:
|
|
231
|
+
|
|
232
|
+
- A pattern containing `/` matches that relative path and everything below it (`build/sub` also excludes `build/sub/x.xml`). Note that `*` in such a pattern also crosses `/` (plain `fnmatch`, unlike gitignore): `build/*.xml` matches `build/sub/x.xml` too.
|
|
233
|
+
- A pattern without `/` matches any single path segment at any depth — `build` excludes `build/`, `a/build/`, and their contents; `*.tmp` excludes temp files everywhere.
|
|
234
|
+
|
|
235
|
+
Patterns are persisted in the sync manifest, per (server, remote path, local folder), so later runs keep excluding without repeating the flag. New `--exclude` patterns are merged into the stored list; `--clear-exclude` wipes it (combine both to replace the list in one run). Files that were synced before becoming excluded are deleted **on both sides** after a confirmation prompt — `--yes` skips the prompt, declining (or running non-interactively without `--yes`) keeps the files, and `--keep-excluded` keeps them without asking. In every case they stop being tracked.
|
|
236
|
+
|
|
188
237
|
**Options:**
|
|
189
238
|
|
|
190
239
|
| Flag | Effect |
|
|
191
240
|
|------|--------|
|
|
192
241
|
| `--force` / `-f` | Transfer all files, bypassing change detection |
|
|
193
242
|
| `--dry-run` / `-n` | Show what would happen without transferring |
|
|
243
|
+
| `--fail-fast` | Stop on the first conflict, invalid XML, or transfer error (runs sequentially; the manifest is saved so the run can resume) |
|
|
244
|
+
| `--jobs N` / `-j` | Number of parallel transfer workers (default: 4); `--jobs 1` for fully sequential behaviour |
|
|
194
245
|
| `--delete` | Remove files and empty folders on the destination that no longer exist on the source |
|
|
246
|
+
| `--exclude PATTERN` / `-e` | Skip matching paths on both sides (repeatable); merged into the list stored for this sync pair |
|
|
247
|
+
| `--clear-exclude` | Wipe the stored exclude list before applying any `--exclude` given in the same run |
|
|
248
|
+
| `--keep-excluded` | Keep previously synced copies of newly excluded paths; only stop tracking them |
|
|
249
|
+
| `--yes` / `-y` | Skip the confirmation prompt when deleting previously synced, newly excluded files |
|
|
195
250
|
| `--verbose` / `-v` | Also print unchanged (skipped) files |
|
|
196
251
|
| `--checkpoint-every N` | Flush the manifest every N files (default: 100); allows interrupted syncs to resume near the point of failure |
|
|
197
252
|
|
|
@@ -210,6 +265,8 @@ exsh --install-completion zsh
|
|
|
210
265
|
exsh --install-completion fish
|
|
211
266
|
```
|
|
212
267
|
|
|
268
|
+
Upgrading? Re-run `exsh --install-completion bash` to pick up fixes to the generated script — it's written once to `~/.bash_completions/` (or equivalent) and isn't updated automatically.
|
|
269
|
+
|
|
213
270
|
## Development
|
|
214
271
|
|
|
215
272
|
```bash
|
|
@@ -181,11 +181,20 @@ EDITOR=nano exsh edit mydata:config.xml
|
|
|
181
181
|
Delete one or more documents from an eXist collection.
|
|
182
182
|
|
|
183
183
|
```
|
|
184
|
-
exsh rm <nick>:<path>...
|
|
184
|
+
exsh rm [--recursive] [--yes] <nick>:<path>...
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
Multiple paths can be supplied in a single invocation. Deletion is permanent — there is no undo.
|
|
188
188
|
|
|
189
|
+
A target that is itself a collection is refused by default (`Error: '<nick>:<path>' is a collection; use -r/--recursive to delete it and everything under it.`) rather than silently deleting the whole subtree. Pass `--recursive` to allow it; unless `--yes` is also given, `rm` first prompts for confirmation naming the collection being removed.
|
|
190
|
+
|
|
191
|
+
### Options
|
|
192
|
+
|
|
193
|
+
| Flag | Effect |
|
|
194
|
+
|------|--------|
|
|
195
|
+
| `--recursive / -r` | Allow a target that is a collection to be deleted, along with everything under it. |
|
|
196
|
+
| `--yes / -y` | Skip the confirmation prompt when deleting a collection. |
|
|
197
|
+
|
|
189
198
|
### Examples
|
|
190
199
|
|
|
191
200
|
```bash
|
|
@@ -194,6 +203,12 @@ exsh rm mydata:reports/2025/old.xml
|
|
|
194
203
|
|
|
195
204
|
# Delete multiple documents at once
|
|
196
205
|
exsh rm mydata:reports/2025/a.xml mydata:reports/2025/b.xml
|
|
206
|
+
|
|
207
|
+
# Delete a whole collection (prompts for confirmation)
|
|
208
|
+
exsh rm --recursive mydata:reports/2025
|
|
209
|
+
|
|
210
|
+
# Delete a whole collection without prompting
|
|
211
|
+
exsh rm --recursive --yes mydata:reports/2025
|
|
197
212
|
```
|
|
198
213
|
|
|
199
214
|
---
|
|
@@ -271,6 +286,7 @@ Execute an XQuery script on an eXist-db server and print the result to stdout.
|
|
|
271
286
|
|
|
272
287
|
```
|
|
273
288
|
exsh exec <nick>[:<path>] [-f FILE] [--no-fix] [--no-validate] [--validator NAME]
|
|
289
|
+
exsh exec --resource <nick>:<path.xql> [-p NAME=VALUE ...]
|
|
274
290
|
```
|
|
275
291
|
|
|
276
292
|
The query is read from `--file` or from stdin. Before sending, `exsh` optionally preprocesses the source and validates it locally:
|
|
@@ -291,10 +307,18 @@ The first installed validator found on `PATH` is used automatically. Use `--vali
|
|
|
291
307
|
|
|
292
308
|
Run `exsh exec --list-validators` to see which validators are available on the current machine.
|
|
293
309
|
|
|
310
|
+
### Executing a stored resource
|
|
311
|
+
|
|
312
|
+
`--resource <nick>:<path.xql>` runs a `.xql`/`.xqm` script that's already stored on the server in place, instead of sending local source. This is a plain `GET` on the resource — the same mechanism eXist uses to execute these MIME types automatically — so it relies on the resource being invoked in its own stored collection context, which matters for scripts that assume that context (e.g. relative `doc()`/`collection()` lookups).
|
|
313
|
+
|
|
314
|
+
`--resource` is mutually exclusive with the `<nick>[:<path>]` argument, and skips local preprocessing/validation entirely, since there's no local source to fix up or check. Repeat `-p/--param NAME=VALUE` to forward query-string parameters, which the resource can read as external variables.
|
|
315
|
+
|
|
294
316
|
### Options
|
|
295
317
|
|
|
296
318
|
| Flag | Description |
|
|
297
319
|
|------|-------------|
|
|
320
|
+
| `--resource <nick>:<path.xql>` | Execute a stored resource in place instead of local code. |
|
|
321
|
+
| `-p / --param NAME=VALUE` | Query-string parameter to forward with `--resource` (repeatable). |
|
|
298
322
|
| `-f / --file FILE` | XQuery file to execute. When omitted, stdin is read. |
|
|
299
323
|
| `--no-fix` | Skip preprocessing (version declaration, namespace imports). |
|
|
300
324
|
| `--no-validate` | Skip local validation even if a validator is installed. |
|
|
@@ -324,6 +348,58 @@ exsh exec mydata:/ --validator basex -f query.xq
|
|
|
324
348
|
|
|
325
349
|
# Check which validators are available locally
|
|
326
350
|
exsh exec --list-validators
|
|
351
|
+
|
|
352
|
+
# Execute a stored resource in place, rather than downloading it first
|
|
353
|
+
exsh exec --resource mydata:/report.xql
|
|
354
|
+
|
|
355
|
+
# ...forwarding query-string parameters as external variables
|
|
356
|
+
exsh exec --resource mydata:/report.xql -p from=2026-01-01 -p to=2026-12-31
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## find
|
|
362
|
+
|
|
363
|
+
Locate documents whose content matches an XPath expression, and optionally delete every match in one step.
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
exsh find <nick>[:<path>] --query <xpath> [--remove] [--yes]
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The expression given to `--query` is evaluated recursively under the target collection (equivalent to `collection(...)//(QUERY)`), and each matching document is printed as a `<nick>:<path>` target — the same format `rm` and every other command accepts. Because `rm` takes its targets as arguments (not from standard input), pipe `find` output through `xargs` to feed it into `rm`:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
exsh find mydata:reports --query 'foo[@type="draft"]' | xargs -r exsh rm
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Deleting matches directly with `--remove` is usually simpler; the pipe form is useful when you want to filter the list first.
|
|
376
|
+
|
|
377
|
+
Without `--remove`, `find` only lists matches — this is inherently a dry run. With `--remove`, matches are deleted as they're printed; unless `--yes` is also given, `find` first prompts for confirmation showing the number of matching documents. If a document reported by the search has already disappeared by the time it is deleted, `find` warns and continues, then exits non-zero.
|
|
378
|
+
|
|
379
|
+
> **Security note:** the `--query` expression is embedded into a server-side XQuery without validation or sandboxing, so it can execute arbitrary XQuery with your configured server credentials (the same trust model as `exsh exec`). Only pass expressions from a trusted source.
|
|
380
|
+
|
|
381
|
+
### Options
|
|
382
|
+
|
|
383
|
+
| Flag | Description |
|
|
384
|
+
|------|-------------|
|
|
385
|
+
| `--query / -q XPATH` | XPath expression to match, evaluated recursively under the target (required). |
|
|
386
|
+
| `--remove` | Delete matching documents instead of just listing them. |
|
|
387
|
+
| `--yes / -y` | Skip the confirmation prompt when used with `--remove`. |
|
|
388
|
+
|
|
389
|
+
### Examples
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
# List documents containing a matching element anywhere in their tree
|
|
393
|
+
exsh find mydata:reports --query 'foo[@type="draft"]'
|
|
394
|
+
|
|
395
|
+
# Match on an attribute nested deeper in the document
|
|
396
|
+
exsh find mydata:/ --query 'section/foo[@type="draft"]'
|
|
397
|
+
|
|
398
|
+
# Delete every match, with a confirmation prompt showing the match count
|
|
399
|
+
exsh find mydata:reports --query 'foo[@type="draft"]' --remove
|
|
400
|
+
|
|
401
|
+
# Delete every match, skipping the confirmation prompt
|
|
402
|
+
exsh find mydata:reports --query 'foo[@type="draft"]' --remove --yes
|
|
327
403
|
```
|
|
328
404
|
|
|
329
405
|
---
|