pcd-cli 0.2.2__tar.gz → 0.2.3__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.
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/PKG-INFO +2 -1
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/README.txt +1 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/pyproject.toml +1 -1
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/catalog.py +1 -1
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/cli/__init__.py +2 -4
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/cli/app.py +2 -3
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/cli/shell.py +9 -19
- pcd_cli-0.2.3/src/pcd_cli/integrations/__init__.py +1 -0
- pcd_cli-0.2.2/src/pcd_cli/shell_integration.py → pcd_cli-0.2.3/src/pcd_cli/integrations/shell.py +24 -7
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/navigation.py +6 -3
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/LICENSE +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/__init__.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/cache.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/cli/config.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/cli/projects.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/cli/roots.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/config.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/filesystem.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/history.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/models.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/picker.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/scanner.py +0 -0
- {pcd_cli-0.2.2 → pcd_cli-0.2.3}/src/pcd_cli/search.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pcd-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Fast project navigator for Bash, Zsh and Fish
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -76,6 +76,7 @@ COMMANDS
|
|
|
76
76
|
pcd config validate Validate the user configuration
|
|
77
77
|
|
|
78
78
|
pcd shell install Install shell integration
|
|
79
|
+
pcd shell init <shell> Print shell integration code
|
|
79
80
|
pcd shell status Show shell integration status
|
|
80
81
|
pcd shell uninstall Remove shell integration
|
|
81
82
|
|
|
@@ -52,6 +52,7 @@ COMMANDS
|
|
|
52
52
|
pcd config validate Validate the user configuration
|
|
53
53
|
|
|
54
54
|
pcd shell install Install shell integration
|
|
55
|
+
pcd shell init <shell> Print shell integration code
|
|
55
56
|
pcd shell status Show shell integration status
|
|
56
57
|
pcd shell uninstall Remove shell integration
|
|
57
58
|
|
|
@@ -9,10 +9,9 @@ from pcd_cli.cli.config import (
|
|
|
9
9
|
from pcd_cli.cli.projects import add, list_projects, project, remove
|
|
10
10
|
from pcd_cli.cli.roots import init, refresh, roots, uninit
|
|
11
11
|
from pcd_cli.cli.shell import (
|
|
12
|
+
init_shell,
|
|
12
13
|
install_shell,
|
|
13
|
-
print_shell_integration,
|
|
14
14
|
shell_commands,
|
|
15
|
-
shell_init,
|
|
16
15
|
shell_status,
|
|
17
16
|
uninstall_shell,
|
|
18
17
|
)
|
|
@@ -24,18 +23,17 @@ __all__ = [
|
|
|
24
23
|
"config_commands",
|
|
25
24
|
"edit_config",
|
|
26
25
|
"init",
|
|
26
|
+
"init_shell",
|
|
27
27
|
"install_shell",
|
|
28
28
|
"list_projects",
|
|
29
29
|
"main",
|
|
30
30
|
"package_version",
|
|
31
31
|
"print_config_path",
|
|
32
|
-
"print_shell_integration",
|
|
33
32
|
"project",
|
|
34
33
|
"refresh",
|
|
35
34
|
"remove",
|
|
36
35
|
"roots",
|
|
37
36
|
"shell_commands",
|
|
38
|
-
"shell_init",
|
|
39
37
|
"shell_status",
|
|
40
38
|
"show_config",
|
|
41
39
|
"uninit",
|
|
@@ -9,11 +9,11 @@ from pcd_cli.catalog import ProjectCatalog
|
|
|
9
9
|
from pcd_cli.cli.config import config_commands
|
|
10
10
|
from pcd_cli.cli.projects import project_commands
|
|
11
11
|
from pcd_cli.cli.roots import root_commands
|
|
12
|
-
from pcd_cli.cli.shell import shell_commands
|
|
12
|
+
from pcd_cli.cli.shell import shell_commands
|
|
13
13
|
from pcd_cli.config import InvalidConfigError
|
|
14
|
+
from pcd_cli.integrations.shell import ShellIntegrationError
|
|
14
15
|
from pcd_cli.models import ExitCode
|
|
15
16
|
from pcd_cli.navigation import navigate_to_project, project_completions
|
|
16
|
-
from pcd_cli.shell_integration import ShellIntegrationError
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
19
|
from click.shell_completion import CompletionItem
|
|
@@ -89,7 +89,6 @@ for command in (
|
|
|
89
89
|
*root_commands,
|
|
90
90
|
config_commands,
|
|
91
91
|
shell_commands,
|
|
92
|
-
shell_init,
|
|
93
92
|
):
|
|
94
93
|
cli.add_command(command)
|
|
95
94
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
|
-
from pcd_cli.
|
|
3
|
+
from pcd_cli.integrations.shell import (
|
|
4
4
|
detect_shell,
|
|
5
|
+
invoking_shell,
|
|
5
6
|
render_shell_integration,
|
|
6
7
|
Shell,
|
|
7
|
-
shell_integration_active,
|
|
8
8
|
ShellIntegration,
|
|
9
9
|
ShellIntegrationError,
|
|
10
10
|
ShellIntegrationState,
|
|
@@ -27,7 +27,7 @@ def install_shell(shell: str | None) -> None:
|
|
|
27
27
|
integration = _shell_integration(shell)
|
|
28
28
|
if integration.install():
|
|
29
29
|
click.echo(f"Installed {integration.shell.value} integration in {integration.config_path}")
|
|
30
|
-
click.echo(f"
|
|
30
|
+
click.echo(f"Reload the current shell with: {integration.reload_command()}")
|
|
31
31
|
return
|
|
32
32
|
|
|
33
33
|
state = integration.state()
|
|
@@ -47,14 +47,15 @@ def install_shell(shell: str | None) -> None:
|
|
|
47
47
|
type=click.Choice([item.value for item in Shell], case_sensitive=False),
|
|
48
48
|
)
|
|
49
49
|
def shell_status(shell: str | None) -> None:
|
|
50
|
-
"""Show whether
|
|
50
|
+
"""Show shell configuration and whether this invocation used a wrapper."""
|
|
51
51
|
integration = _shell_integration(shell)
|
|
52
52
|
click.echo(f"Shell: {integration.shell.value}")
|
|
53
53
|
click.echo(f"Config: {integration.config_path}")
|
|
54
54
|
click.echo(f"Configured: {integration.state().value}")
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
wrapper: Shell | None = invoking_shell()
|
|
57
|
+
invoked = "no" if wrapper is None else f"yes ({wrapper.value})"
|
|
58
|
+
click.echo(f"Invoked through wrapper: {invoked}")
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
@shell_commands.command("uninstall")
|
|
@@ -78,30 +79,19 @@ def uninstall_shell(shell: str | None) -> None:
|
|
|
78
79
|
click.echo(f"Shell integration is not installed in {integration.config_path}")
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
@shell_commands.command("
|
|
82
|
+
@shell_commands.command("init")
|
|
82
83
|
@click.argument(
|
|
83
84
|
"shell",
|
|
84
85
|
required=False,
|
|
85
86
|
type=click.Choice([item.value for item in Shell], case_sensitive=False),
|
|
86
87
|
)
|
|
87
88
|
@click.pass_context
|
|
88
|
-
def
|
|
89
|
+
def init_shell(ctx: click.Context, shell: str | None) -> None:
|
|
89
90
|
"""Print shell integration for manual dotfile management."""
|
|
90
91
|
selected = _selected_shell(shell)
|
|
91
92
|
click.echo(render_shell_integration(selected, _registered_command_names(ctx)), nl=False)
|
|
92
93
|
|
|
93
94
|
|
|
94
|
-
@click.command("shell-init", hidden=True)
|
|
95
|
-
@click.argument("shell", type=click.Choice([item.value for item in Shell], case_sensitive=False))
|
|
96
|
-
@click.pass_context
|
|
97
|
-
def shell_init(ctx: click.Context, shell: str) -> None:
|
|
98
|
-
"""Backward-compatible alias for `pcd shell print`."""
|
|
99
|
-
click.echo(
|
|
100
|
-
render_shell_integration(Shell(shell.casefold()), _registered_command_names(ctx)),
|
|
101
|
-
nl=False,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
105
95
|
def _shell_integration(shell: str | None) -> ShellIntegration:
|
|
106
96
|
return ShellIntegration.for_shell(_selected_shell(shell))
|
|
107
97
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""External environment integrations."""
|
pcd_cli-0.2.2/src/pcd_cli/shell_integration.py → pcd_cli-0.2.3/src/pcd_cli/integrations/shell.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Generate, install, and inspect shell integration for pcd."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import shlex
|
|
4
5
|
import stat
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from enum import StrEnum
|
|
@@ -11,6 +12,7 @@ from typing import Literal, Self
|
|
|
11
12
|
from pcd_cli.filesystem import atomic_write, file_lock
|
|
12
13
|
|
|
13
14
|
SHELL_MODE_ENV = "PCD_SHELL"
|
|
15
|
+
SHELL_WRAPPER_ENV = "PCD_WRAPPER"
|
|
14
16
|
# Internal success status: stdout contains the destination path when this is returned.
|
|
15
17
|
SHELL_CD_EXIT_CODE = 10
|
|
16
18
|
_MANAGED_BLOCK_START = "# >>> pcd shell integration >>>"
|
|
@@ -49,6 +51,9 @@ class ShellIntegration:
|
|
|
49
51
|
def state(self) -> ShellIntegrationState:
|
|
50
52
|
return _integration_state(self._read(), self.shell)
|
|
51
53
|
|
|
54
|
+
def reload_command(self) -> str:
|
|
55
|
+
return f"source {shlex.quote(str(self.config_path))}"
|
|
56
|
+
|
|
52
57
|
def install(self) -> bool:
|
|
53
58
|
"""Install the managed block. Return whether the config changed."""
|
|
54
59
|
with file_lock(self.config_path):
|
|
@@ -128,6 +133,14 @@ def shell_integration_active() -> bool:
|
|
|
128
133
|
return os.environ.get(SHELL_MODE_ENV) == "1"
|
|
129
134
|
|
|
130
135
|
|
|
136
|
+
def invoking_shell() -> Shell | None:
|
|
137
|
+
"""Identify the wrapper for this invocation, not the parent shell's state."""
|
|
138
|
+
try:
|
|
139
|
+
return Shell(os.environ.get(SHELL_WRAPPER_ENV, ""))
|
|
140
|
+
except ValueError:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
131
144
|
def inactive_shell_message() -> str:
|
|
132
145
|
"""Explain how to activate directory changes when the wrapper is not running."""
|
|
133
146
|
try:
|
|
@@ -146,16 +159,16 @@ def inactive_shell_message() -> str:
|
|
|
146
159
|
)
|
|
147
160
|
return (
|
|
148
161
|
f"Shell integration is configured in {integration.config_path} but is not active in "
|
|
149
|
-
f"this shell.
|
|
162
|
+
f"this shell. Reload it with: {integration.reload_command()}"
|
|
150
163
|
)
|
|
151
164
|
|
|
152
165
|
|
|
153
166
|
def render_managed_block(shell: Shell) -> str:
|
|
154
167
|
"""Render the small persistent block written into the shell startup file."""
|
|
155
168
|
command = (
|
|
156
|
-
f'eval "$(command pcd shell
|
|
169
|
+
f'eval "$(command pcd shell init {shell.value})"'
|
|
157
170
|
if shell is not Shell.FISH
|
|
158
|
-
else f"command pcd shell
|
|
171
|
+
else f"command pcd shell init {shell.value} | source"
|
|
159
172
|
)
|
|
160
173
|
return f"{_MANAGED_BLOCK_START}\n{command}\n{_MANAGED_BLOCK_END}\n"
|
|
161
174
|
|
|
@@ -179,9 +192,8 @@ def _integration_state(content: str, shell: Shell) -> ShellIntegrationState:
|
|
|
179
192
|
if bounds is not None:
|
|
180
193
|
return ShellIntegrationState.MANAGED
|
|
181
194
|
|
|
182
|
-
|
|
183
|
-
current
|
|
184
|
-
if legacy in content or current in content:
|
|
195
|
+
current = f"pcd shell init {shell.value}"
|
|
196
|
+
if current in content:
|
|
185
197
|
return ShellIntegrationState.MANUAL
|
|
186
198
|
return ShellIntegrationState.ABSENT
|
|
187
199
|
|
|
@@ -214,6 +226,7 @@ def _posix(completion: Literal["bash_source", "zsh_source"], commands: tuple[str
|
|
|
214
226
|
return dedent(
|
|
215
227
|
f"""\
|
|
216
228
|
pcd() {{
|
|
229
|
+
local -x {SHELL_WRAPPER_ENV}={completion.removesuffix("_source")}
|
|
217
230
|
if [ -n "${{_PCD_COMPLETE:-}}" ]; then
|
|
218
231
|
command pcd "$@"
|
|
219
232
|
return $?
|
|
@@ -260,6 +273,7 @@ def _fish(commands: tuple[str, ...]) -> str:
|
|
|
260
273
|
return dedent(
|
|
261
274
|
f"""\
|
|
262
275
|
function pcd
|
|
276
|
+
set -lx {SHELL_WRAPPER_ENV} fish
|
|
263
277
|
if set -q _PCD_COMPLETE
|
|
264
278
|
command pcd $argv
|
|
265
279
|
return $status
|
|
@@ -277,7 +291,10 @@ def _fish(commands: tuple[str, ...]) -> str:
|
|
|
277
291
|
return $status
|
|
278
292
|
end
|
|
279
293
|
|
|
280
|
-
set -l output (
|
|
294
|
+
set -l output (begin
|
|
295
|
+
set -lx {SHELL_MODE_ENV} 1
|
|
296
|
+
command pcd $argv
|
|
297
|
+
end)
|
|
281
298
|
set -l code $status
|
|
282
299
|
if test $code -eq {SHELL_CD_EXIT_CODE}
|
|
283
300
|
cd -- "$output"
|
|
@@ -7,12 +7,12 @@ from click.shell_completion import CompletionItem
|
|
|
7
7
|
|
|
8
8
|
from pcd_cli.catalog import ProjectCatalog
|
|
9
9
|
from pcd_cli.filesystem import format_path
|
|
10
|
-
from pcd_cli.
|
|
11
|
-
from pcd_cli.shell_integration import (
|
|
10
|
+
from pcd_cli.integrations.shell import (
|
|
12
11
|
inactive_shell_message,
|
|
13
12
|
SHELL_CD_EXIT_CODE,
|
|
14
13
|
shell_integration_active,
|
|
15
14
|
)
|
|
15
|
+
from pcd_cli.models import ExitCode, ProjectSource
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from collections.abc import Sequence
|
|
@@ -65,7 +65,10 @@ def project_completions(value: str) -> list[CompletionItem]:
|
|
|
65
65
|
|
|
66
66
|
def _open_project(catalog: ProjectCatalog, project: Project) -> None:
|
|
67
67
|
if project.path.is_dir():
|
|
68
|
-
|
|
68
|
+
try:
|
|
69
|
+
catalog.record_usage(project)
|
|
70
|
+
except OSError as exc:
|
|
71
|
+
click.echo(f"Warning: could not write usage history: {exc}", err=True)
|
|
69
72
|
elif project.source is ProjectSource.MANUAL:
|
|
70
73
|
click.echo(
|
|
71
74
|
f"Project path no longer exists: {format_path(project.display_path)}",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|