dotx 3.3.1__tar.gz → 3.3.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.
- {dotx-3.3.1/src/dotx.egg-info → dotx-3.3.3}/PKG-INFO +1 -1
- {dotx-3.3.1 → dotx-3.3.3}/pyproject.toml +1 -1
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/cli.py +1 -1
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/commands/database.py +17 -4
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/commands/install_cmd.py +11 -4
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/options.py +8 -7
- {dotx-3.3.1 → dotx-3.3.3/src/dotx.egg-info}/PKG-INFO +1 -1
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_cli_database.py +42 -1
- {dotx-3.3.1 → dotx-3.3.3}/LICENSE +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/MANIFEST.in +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/README.md +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/setup.cfg +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/__init__.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/always-create +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/commands/__init__.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/commands/path_cmd.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/commands/progress.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/commands/uninstall_cmd.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/database.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/dotxignore +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/hierarchy.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/ignore.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/install.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/installed-schema.sql +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/plan.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx/uninstall.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx.egg-info/SOURCES.txt +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx.egg-info/dependency_links.txt +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx.egg-info/entry_points.txt +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx.egg-info/requires.txt +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/src/dotx.egg-info/top_level.txt +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_always_create.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_cli.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_ignore.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_ignore_rules.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_install.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_options.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_path_which.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_plan.py +0 -0
- {dotx-3.3.1 → dotx-3.3.3}/tests/test_uninstall.py +0 -0
|
@@ -85,7 +85,7 @@ def register_commands(app: typer.Typer):
|
|
|
85
85
|
packages = db.get_all_packages()
|
|
86
86
|
|
|
87
87
|
if not packages:
|
|
88
|
-
console.print("[yellow]No packages
|
|
88
|
+
console.print("[yellow]No packages in database. Run `dotx sync --package-root <dir>` to rebuild from existing symlinks.[/yellow]")
|
|
89
89
|
return
|
|
90
90
|
|
|
91
91
|
if as_commands:
|
|
@@ -100,17 +100,30 @@ def register_commands(app: typer.Typer):
|
|
|
100
100
|
table.add_column("Package", style="cyan", no_wrap=True)
|
|
101
101
|
table.add_column("Files", justify="right", style="magenta")
|
|
102
102
|
table.add_column("Last Install", style="green")
|
|
103
|
+
table.add_column("Status", style="green")
|
|
103
104
|
|
|
105
|
+
missing_count = 0
|
|
104
106
|
for pkg in packages:
|
|
105
107
|
# Show full package path (package_root / package_name) as documented
|
|
106
|
-
package_path =
|
|
108
|
+
package_path = Path(pkg["package_root"]) / pkg["package_name"]
|
|
107
109
|
file_count = str(pkg["file_count"])
|
|
108
110
|
latest = _format_timestamp(pkg["latest_install"])
|
|
109
|
-
|
|
111
|
+
|
|
112
|
+
# Check if source still exists
|
|
113
|
+
if package_path.exists():
|
|
114
|
+
status = "[green]✓[/green]"
|
|
115
|
+
else:
|
|
116
|
+
status = "[yellow]⚠ source missing[/yellow]"
|
|
117
|
+
missing_count += 1
|
|
118
|
+
|
|
119
|
+
table.add_row(str(package_path), file_count, latest, status)
|
|
110
120
|
|
|
111
121
|
console.print()
|
|
112
122
|
console.print(table)
|
|
113
|
-
console.print(f"\n[bold]Total: {len(packages)} package(s)[/bold]
|
|
123
|
+
console.print(f"\n[bold]Total: {len(packages)} package(s)[/bold]")
|
|
124
|
+
if missing_count > 0:
|
|
125
|
+
console.print(f"[yellow]⚠ {missing_count} package(s) have missing source directories. Run `dotx verify` for details.[/yellow]")
|
|
126
|
+
console.print()
|
|
114
127
|
|
|
115
128
|
logger.info("list finished")
|
|
116
129
|
|
|
@@ -99,11 +99,18 @@ def register_command(app: typer.Typer):
|
|
|
99
99
|
if total_dirs:
|
|
100
100
|
summary_parts.append(f"{total_dirs} dir(s)")
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
if summary_parts:
|
|
103
|
+
summary = " and ".join(summary_parts)
|
|
104
|
+
if is_dry_run(ctx):
|
|
105
|
+
console.print(f"\n[yellow][DRY RUN] Would install {summary} from {len(sources)} package(s)[/yellow]")
|
|
106
|
+
else:
|
|
107
|
+
console.print(f"\n[green]✓ Installed {summary} from {len(sources)} package(s)[/green]")
|
|
105
108
|
else:
|
|
106
|
-
|
|
109
|
+
# Empty package(s) - nothing to install
|
|
110
|
+
if is_dry_run(ctx):
|
|
111
|
+
console.print(f"\n[yellow][DRY RUN] Would install nothing from {len(sources)} package(s) (nothing to record in database)[/yellow]")
|
|
112
|
+
else:
|
|
113
|
+
console.print(f"\n[green]✓ Installed nothing from {len(sources)} package(s) (nothing to record in database)[/green]")
|
|
107
114
|
else:
|
|
108
115
|
console.print("[red]✗ Refusing to install - conflicts detected[/red]")
|
|
109
116
|
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides convenience functions for accessing user-data on the associated context.
|
|
3
3
|
|
|
4
|
-
Note: typer is built on click, so we use
|
|
4
|
+
Note: typer is built on click, so we use typer.Context for type annotations.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
|
+
import typer
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
def set_option(option: str, value: Any, ctx:
|
|
14
|
+
def set_option(option: str, value: Any, ctx: typer.Context | None = None) -> bool:
|
|
14
15
|
"""
|
|
15
16
|
Set an option value in the Typer context.
|
|
16
17
|
|
|
@@ -28,7 +29,7 @@ def set_option(option: str, value: Any, ctx: click.Context | None = None) -> boo
|
|
|
28
29
|
return False
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
def get_option(option: str, default_for_option: Any = None, ctx:
|
|
32
|
+
def get_option(option: str, default_for_option: Any = None, ctx: typer.Context | None = None) -> Any:
|
|
32
33
|
"""
|
|
33
34
|
Get an option value from the context.
|
|
34
35
|
|
|
@@ -44,22 +45,22 @@ def get_option(option: str, default_for_option: Any = None, ctx: click.Context |
|
|
|
44
45
|
return default_for_option
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
def is_verbose_mode(ctx:
|
|
48
|
+
def is_verbose_mode(ctx: typer.Context | None = None) -> bool:
|
|
48
49
|
"""Check if verbose mode is enabled."""
|
|
49
50
|
return get_option("VERBOSE", False, ctx)
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
def is_debug_mode(ctx:
|
|
53
|
+
def is_debug_mode(ctx: typer.Context | None = None) -> bool:
|
|
53
54
|
"""Check if debug mode is enabled."""
|
|
54
55
|
return get_option("DEBUG", False, ctx)
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
def is_dry_run(ctx:
|
|
58
|
+
def is_dry_run(ctx: typer.Context | None = None) -> bool:
|
|
58
59
|
"""Check if dry-run mode is enabled."""
|
|
59
60
|
return get_option("DRYRUN", False, ctx)
|
|
60
61
|
|
|
61
62
|
|
|
62
|
-
def is_xdg_mode(ctx:
|
|
63
|
+
def is_xdg_mode(ctx: typer.Context | None = None) -> bool:
|
|
63
64
|
"""Check if XDG mode is enabled."""
|
|
64
65
|
return get_option("XDG", False, ctx)
|
|
65
66
|
|
|
@@ -301,7 +301,7 @@ def test_cli_list_no_packages(tmp_path, monkeypatch):
|
|
|
301
301
|
result = runner.invoke(app, ["list"])
|
|
302
302
|
|
|
303
303
|
assert result.exit_code == 0
|
|
304
|
-
assert "No packages
|
|
304
|
+
assert "No packages in database" in result.output
|
|
305
305
|
|
|
306
306
|
|
|
307
307
|
def test_cli_list_with_packages(tmp_path, monkeypatch):
|
|
@@ -866,3 +866,44 @@ def test_list_shows_correct_package_names_after_install(tmp_path, monkeypatch):
|
|
|
866
866
|
result = runner.invoke(app, ["list"])
|
|
867
867
|
assert result.exit_code == 0, f"list failed: {result.output}"
|
|
868
868
|
assert "2 package(s)" in result.output # Should show 2 packages
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def test_cli_list_shows_missing_source_status(tmp_path, monkeypatch):
|
|
872
|
+
"""Test 'dotx list' shows status indicator when source package is missing."""
|
|
873
|
+
import shutil
|
|
874
|
+
|
|
875
|
+
# Override XDG_DATA_HOME
|
|
876
|
+
data_dir = tmp_path / "config"
|
|
877
|
+
data_dir.mkdir()
|
|
878
|
+
monkeypatch.setenv("XDG_DATA_HOME", str(data_dir))
|
|
879
|
+
|
|
880
|
+
# Create and install packages
|
|
881
|
+
source1 = tmp_path / "source1"
|
|
882
|
+
source1.mkdir()
|
|
883
|
+
(source1 / "file1").write_text("content")
|
|
884
|
+
|
|
885
|
+
source2 = tmp_path / "source2"
|
|
886
|
+
source2.mkdir()
|
|
887
|
+
(source2 / "file2").write_text("content")
|
|
888
|
+
|
|
889
|
+
target = tmp_path / "target"
|
|
890
|
+
target.mkdir()
|
|
891
|
+
|
|
892
|
+
runner = CliRunner()
|
|
893
|
+
|
|
894
|
+
# Install both packages
|
|
895
|
+
result = runner.invoke(app, [f"--target={target}", "install", str(source1)])
|
|
896
|
+
assert result.exit_code == 0
|
|
897
|
+
result = runner.invoke(app, [f"--target={target}", "install", str(source2)])
|
|
898
|
+
assert result.exit_code == 0
|
|
899
|
+
|
|
900
|
+
# Delete one source package
|
|
901
|
+
shutil.rmtree(source1)
|
|
902
|
+
assert not source1.exists()
|
|
903
|
+
|
|
904
|
+
# List packages - should show warning for missing source
|
|
905
|
+
result = runner.invoke(app, ["list"])
|
|
906
|
+
assert result.exit_code == 0
|
|
907
|
+
# The warning message at the bottom should be visible
|
|
908
|
+
assert "1 package(s) have missing source directories" in result.output
|
|
909
|
+
assert "dotx verify" in result.output
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|