feat(cli): enhance exclusion pattern handling and improve user feedback

This commit is contained in:
akadeepesh
2026-10-07 11:17:08 +05:30
parent 1f920f81e5
commit e7b62f359e
3 changed files with 22 additions and 39 deletions
+17 -11
View File
@@ -927,6 +927,18 @@ def _run(
if saved:
project_cfg = load_project_config(project_dir)
# ── Include/exclude inputs shared by every scan mode ─────────────────────
# CLI --exclude/-e patterns plus any standing always_exclude patterns from
# project config — both are additive, persistent behavior comes from
# config.json so it doesn't need to be re-typed every run. always_include
# is a standing negation. Built once here so standard and --git-changes
# runs honour project config identically.
normalized_exclude = [normalize_pattern(p) for p in extra_exclude or []]
normalized_exclude += [normalize_pattern(p) for p in project_cfg.always_exclude]
force_include = build_force_include_spec(
[normalize_pattern(p) for p in project_cfg.always_include]
)
# ── Git-changes mode ─────────────────────────────────────────────────────
if git_changes:
with console.status("[cyan]Querying git for changed files…[/]", spinner="dots"):
@@ -939,7 +951,7 @@ def _run(
print_git_deleted_and_submodules(git_result)
if git_result.is_empty:
info("Nothing to package — working tree is clean.")
info("Nothing to compress in this one — working tree is clean.")
return
scan_label, scan_detail = git_scan_label_and_detail(git_result)
@@ -953,27 +965,21 @@ def _run(
large_file_warn_bytes=int(
project_cfg.limits.max_file_size_mb * 1024 * 1024
),
extra_exclude=normalized_exclude if normalized_exclude else None,
include_only=include_only if include_only else None,
force_include=force_include,
)
else:
# ── Build exclusion spec ─────────────────────────────────────────────
gitignore_path = None if no_gitignore else (project_dir / ".gitignore")
# CLI --exclude/-e patterns plus any standing always_exclude patterns
# from project config — both are additive, persistent behavior comes
# from config.json so it doesn't need to be re-typed every run.
normalized_exclude = [normalize_pattern(p) for p in extra_exclude or []]
normalized_exclude += [normalize_pattern(p) for p in project_cfg.always_exclude]
with console.status("[cyan]Building exclusion rules…[/]", spinner="dots"):
spec = build_spec(
rule_modules=detection.rule_modules,
extra_exclude=normalized_exclude if normalized_exclude else None,
gitignore_path=gitignore_path,
)
force_include = build_force_include_spec(
[normalize_pattern(p) for p in project_cfg.always_include]
)
# ── Resolve files ─────────────────────────────────────────────────────
with console.status("[cyan]Scanning project files…[/]", spinner="dots"):
@@ -1027,7 +1033,7 @@ def _run(
return
if not resolved.included:
err("Nothing to package — all files were excluded.")
err("Nothing to compress in this one — all files were excluded.")
info("Try contextzip include PATH or -i PATH to override.")
return
+1 -28
View File
@@ -15,38 +15,11 @@ from pathlib import Path
from rich.console import Console
from contextzip.cli_display import print_ai_selection, err, warn, info
from contextzip.filters import normalize_pattern # noqa: F401 (re-exported for cli.py)
console = Console()
# ---------------------------------------------------------------------------
# Pattern normalisation
# ---------------------------------------------------------------------------
def normalize_pattern(p: str) -> str:
"""
Canonicalise a user-supplied exclusion pattern.
Rules applied in order:
1. Strip a leading ``./`` or ``.\\`` so that ``./CHANGELOG.md``
and ``CHANGELOG.md`` are treated identically.
2. Replace every backslash with a forward slash for cross-platform
consistency (Windows paths entered on the CLI).
3. Collapse ``folder/*`` → ``folder/`` so that gitignore-style
directory globs work as expected.
"""
# 1. Strip leading ./ or .\\
if p.startswith("./") or p.startswith(".\\"):
p = p[2:]
# 2. Normalise path separators
p = p.replace("\\", "/")
# 3. folder/* → folder/
if p.endswith("/*"):
p = p[:-1]
return p
# ---------------------------------------------------------------------------
# AI selection (full run — produces ZIP)
# ---------------------------------------------------------------------------
+4
View File
@@ -64,6 +64,10 @@ def print_detection(detection, *, con: Console = console) -> None:
"""One line: what was detected and how confident contextzip is."""
if detection.is_unknown:
ok("Detected", "unknown ecosystem, base rules only", con=con)
info(
"Don't see your language? Add it here: https://example.com",
con=con,
)
return
parts = []