shellscripts 0.23.1__py3-none-any.whl

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.
Files changed (45) hide show
  1. shell_scripts/__init__.py +5 -0
  2. shell_scripts/__main__.py +5 -0
  3. shell_scripts/commands/__init__.py +81 -0
  4. shell_scripts/commands/_dc_common.py +138 -0
  5. shell_scripts/commands/ai_install.py +453 -0
  6. shell_scripts/commands/bin_links.py +78 -0
  7. shell_scripts/commands/claude.py +56 -0
  8. shell_scripts/commands/clean.py +98 -0
  9. shell_scripts/commands/codex.py +107 -0
  10. shell_scripts/commands/copilot.py +54 -0
  11. shell_scripts/commands/dicom2jpg.py +98 -0
  12. shell_scripts/commands/dicomviewer.py +83 -0
  13. shell_scripts/commands/diff_cmd.py +50 -0
  14. shell_scripts/commands/doxygen_cmd.py +159 -0
  15. shell_scripts/commands/edit_cmd.py +50 -0
  16. shell_scripts/commands/gemini.py +54 -0
  17. shell_scripts/commands/kiro.py +54 -0
  18. shell_scripts/commands/opencode.py +54 -0
  19. shell_scripts/commands/pdf_crop.py +520 -0
  20. shell_scripts/commands/pdf_merge.py +180 -0
  21. shell_scripts/commands/pdf_split_by_format.py +243 -0
  22. shell_scripts/commands/pdf_split_by_toc.py +197 -0
  23. shell_scripts/commands/pdf_tiler_090.py +61 -0
  24. shell_scripts/commands/pdf_tiler_100.py +61 -0
  25. shell_scripts/commands/pdf_toc_clean.py +175 -0
  26. shell_scripts/commands/pi.py +61 -0
  27. shell_scripts/commands/req_cmd.py +362 -0
  28. shell_scripts/commands/tests_cmd.py +87 -0
  29. shell_scripts/commands/ubuntu_dark_theme.py +64 -0
  30. shell_scripts/commands/venv_cmd.py +82 -0
  31. shell_scripts/commands/video2h264.py +89 -0
  32. shell_scripts/commands/video2h265.py +87 -0
  33. shell_scripts/commands/view_cmd.py +50 -0
  34. shell_scripts/commands/vscode_cmd.py +42 -0
  35. shell_scripts/commands/vsinsider_cmd.py +42 -0
  36. shell_scripts/config.py +328 -0
  37. shell_scripts/core.py +324 -0
  38. shell_scripts/utils.py +455 -0
  39. shell_scripts/version_check.py +323 -0
  40. shellscripts-0.23.1.dist-info/METADATA +310 -0
  41. shellscripts-0.23.1.dist-info/RECORD +45 -0
  42. shellscripts-0.23.1.dist-info/WHEEL +5 -0
  43. shellscripts-0.23.1.dist-info/entry_points.txt +3 -0
  44. shellscripts-0.23.1.dist-info/licenses/LICENSE +674 -0
  45. shellscripts-0.23.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ from shell_scripts.utils import print_error
6
+
7
+ PROGRAM = "shellscripts"
8
+ DESCRIPTION = "Create or update command symlinks in $HOME/bin."
9
+
10
+
11
+ def print_help(version):
12
+ print(f"Usage: {PROGRAM} bin-links [options] ({version})")
13
+ print()
14
+ print("bin-links options:")
15
+ print(
16
+ " <directory> - Source directory containing files to link (default: current dir)."
17
+ )
18
+ print(" --dest <dir> - Destination directory for symlinks (default: $HOME/bin).")
19
+ print(" --help - Show this help message.")
20
+
21
+
22
+ def run(args):
23
+ src_dir = Path.cwd()
24
+ dest_dir = Path.home() / "bin"
25
+
26
+ i = 0
27
+ while i < len(args):
28
+ if args[i] == "--dest":
29
+ if i + 1 >= len(args):
30
+ print_error("--dest requires a directory path.")
31
+ return 1
32
+ dest_dir = Path(args[i + 1])
33
+ i += 2
34
+ elif not args[i].startswith("-"):
35
+ src_dir = Path(args[i])
36
+ i += 1
37
+ else:
38
+ print_error(f"Unknown option: {args[i]}")
39
+ return 1
40
+
41
+ if not src_dir.is_dir():
42
+ print_error(f"Source directory not found: {src_dir}")
43
+ return 1
44
+
45
+ dest_dir.mkdir(parents=True, exist_ok=True)
46
+
47
+ for file_path in sorted(src_dir.iterdir()):
48
+ if file_path.is_dir():
49
+ continue
50
+
51
+ filename = file_path.name
52
+ abs_src = file_path.resolve()
53
+ link_name = filename.removesuffix(".sh")
54
+ target_link = dest_dir / link_name
55
+
56
+ print(f"Examining: {filename}...")
57
+
58
+ if target_link.exists() or target_link.is_symlink():
59
+ if target_link.is_symlink():
60
+ current_target = target_link.resolve()
61
+ if current_target == abs_src:
62
+ print(f" [OK] Link already points correctly to {filename}.")
63
+ else:
64
+ print(" [UPDATE] Link points elsewhere. Updating...")
65
+ target_link.unlink()
66
+ target_link.symlink_to(abs_src)
67
+ else:
68
+ print(
69
+ f" [ERROR] '{target_link}' is a regular file. "
70
+ f"Skipping to avoid data loss.",
71
+ file=sys.stderr,
72
+ )
73
+ else:
74
+ print(" [NEW] Creating symlink...")
75
+ target_link.symlink_to(abs_src)
76
+
77
+ print("--- Operation completed ---")
78
+ return 0
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python3
2
+ """@brief Claude CLI launcher in project context.
3
+
4
+ @details Ensures project-root prerequisite and executable availability before
5
+ blocking subprocess invocation of user-local Claude binary.
6
+ @satisfies REQ-017, REQ-055, REQ-056, REQ-064
7
+ """
8
+
9
+ import subprocess
10
+ from pathlib import Path
11
+
12
+ from shell_scripts.utils import require_project_root, require_commands
13
+
14
+ ## @var PROGRAM
15
+ # @brief Base CLI program name used in help output.
16
+ # @details Constant identifier for usage-line rendering in command help.
17
+ PROGRAM = "shellscripts"
18
+ ## @var DESCRIPTION
19
+ # @brief One-line command description for dispatcher help surfaces.
20
+ # @details Exposed by command registry introspection (`get_all_commands`).
21
+ DESCRIPTION = "Launch Claude CLI with skip-permissions in the project context."
22
+
23
+
24
+ def print_help(version):
25
+ """@brief Print command-specific help for `claude`.
26
+
27
+ @details Emits usage and pass-through argument behavior for deterministic
28
+ terminal rendering; does not mutate process state.
29
+ @param version {str} CLI version string propagated by dispatcher.
30
+ @return {None} Writes help text to stdout.
31
+ @satisfies DES-008
32
+ """
33
+
34
+ print(f"Usage: {PROGRAM} claude [args...] ({version})")
35
+ print()
36
+ print("claude options:")
37
+ print(" All arguments are passed through to the Claude CLI.")
38
+ print(" --help - Show this help message.")
39
+
40
+
41
+ def run(args):
42
+ """@brief Launch Claude CLI after external executable validation.
43
+
44
+ @details Resolves project root, resolves user-local Claude executable path,
45
+ validates executable availability, then executes command via subprocess.
46
+ @param args {list[str]} Additional CLI args forwarded to Claude.
47
+ @return {int} Child process return code.
48
+ @satisfies REQ-017, REQ-055, REQ-056, REQ-064
49
+ """
50
+
51
+ require_project_root()
52
+ claude_bin = Path.home() / ".claude" / "bin" / "claude"
53
+ cmd = [str(claude_bin), "--dangerously-skip-permissions"] + args
54
+ cmd[0] = require_commands(cmd[0])
55
+ result = subprocess.run(cmd)
56
+ return result.returncode
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from shell_scripts.utils import require_project_root, print_error
7
+
8
+ PROGRAM = "shellscripts"
9
+ DESCRIPTION = "Find and delete cache directories under the project root."
10
+
11
+ CACHE_DIRS = [
12
+ "__pycache__",
13
+ ".pytest_cache",
14
+ ".mypy_cache",
15
+ ".cache",
16
+ ".npm",
17
+ ".parcel-cache",
18
+ ".eslintcache",
19
+ ".sass-cache",
20
+ ".terragrunt-cache",
21
+ "htmlcov",
22
+ ]
23
+
24
+
25
+ def print_help(version):
26
+ print(f"Usage: {PROGRAM} clean [options] ({version})")
27
+ print()
28
+ print("clean options:")
29
+ print(" <directory> - Target directory to search (default: git project root).")
30
+ print(" --yes - Skip confirmation prompt and delete immediately.")
31
+ print(" --help - Show this help message.")
32
+
33
+
34
+ def run(args):
35
+ project_root = require_project_root()
36
+ target_dir = project_root
37
+ auto_confirm = False
38
+
39
+ for arg in args:
40
+ if arg == "--yes":
41
+ auto_confirm = True
42
+ elif not arg.startswith("-"):
43
+ target_dir = Path(arg)
44
+ else:
45
+ print_error(f"Unknown option: {arg}")
46
+ return 1
47
+
48
+ os.chdir(project_root)
49
+
50
+ print(f"--- Searching for cache directories in: {target_dir} ---")
51
+ print("Please wait, scanning...")
52
+
53
+ found = []
54
+ for dir_name in CACHE_DIRS:
55
+ for root, dirs, _ in os.walk(target_dir):
56
+ if dir_name in dirs:
57
+ found.append(os.path.join(root, dir_name))
58
+
59
+ if not found:
60
+ print("No cache directories found.")
61
+ return 0
62
+
63
+ print()
64
+ print("Found the following directories:")
65
+ print("---------------------------------")
66
+ for path in found:
67
+ print(f" -> {path}")
68
+ print("---------------------------------")
69
+ print(f"Total directories found: {len(found)}")
70
+ print()
71
+
72
+ if auto_confirm:
73
+ confirm = "y"
74
+ else:
75
+ try:
76
+ confirm = input(
77
+ "WARNING: Are you sure you want to permanently DELETE "
78
+ "these directories? (y/N): "
79
+ )
80
+ except (EOFError, KeyboardInterrupt):
81
+ print()
82
+ confirm = "n"
83
+
84
+ if confirm.lower() == "y":
85
+ print()
86
+ print("Deleting...")
87
+ count = 0
88
+ for path in found:
89
+ shutil.rmtree(path, ignore_errors=True)
90
+ count += 1
91
+ print(f"Deleted: {path}")
92
+ print()
93
+ print(f"Cleanup complete! {count} directories removed.")
94
+ else:
95
+ print()
96
+ print("Operation aborted. No files were touched.")
97
+
98
+ return 0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ """@brief Codex CLI launcher with project-context auth file synchronization.
3
+
4
+ @details Ensures project-local Codex context is prepared before command
5
+ execution. The launcher copies auth state from `~/.codex/auth.json` into
6
+ `<project>/.codex/auth.json` before CLI launch, sets
7
+ `CODEX_HOME=<project>/.codex`, executes `codex --yolo`, then copies auth state
8
+ back from project path to home path before returning, emitting one
9
+ informational output line for each copy direction.
10
+ @satisfies REQ-014, REQ-043, REQ-044, REQ-064
11
+ """
12
+
13
+ import os
14
+ import shutil
15
+ import subprocess
16
+ from pathlib import Path
17
+
18
+ from shell_scripts.utils import print_info, require_project_root, require_commands
19
+
20
+ ## @var PROGRAM
21
+ # @brief Base CLI program name used in help output.
22
+ # @details Constant identifier for usage-line rendering in command help.
23
+ PROGRAM = "shellscripts"
24
+ ## @var DESCRIPTION
25
+ # @brief One-line command description for dispatcher help surfaces.
26
+ # @details Exposed by command registry introspection (`get_all_commands`).
27
+ DESCRIPTION = "Launch OpenAI Codex CLI in the project context."
28
+
29
+
30
+ def print_help(version: str) -> None:
31
+ """@brief Print command-specific help for `codex`.
32
+
33
+ @details Emits usage and pass-through argument behavior for deterministic
34
+ terminal rendering; does not mutate process state.
35
+ @param version {str} CLI version string propagated by dispatcher.
36
+ @return {None} Writes help text to stdout.
37
+ @satisfies DES-008
38
+ """
39
+ print(f"Usage: {PROGRAM} codex [args...] ({version})")
40
+ print()
41
+ print("codex options:")
42
+ print(" All arguments are passed through to the Codex CLI.")
43
+ print(" --help - Show this help message.")
44
+
45
+
46
+ def _copy_auth_file(
47
+ source_path: Path,
48
+ destination_path: Path,
49
+ direction_label: str,
50
+ ) -> None:
51
+ """@brief Copy Codex auth file while replacing destination file or symlink.
52
+
53
+ @details Ensures destination parent directory exists, removes an existing
54
+ destination entry when it is a file or symbolic link, then copies source
55
+ bytes to destination preserving metadata with `shutil.copy2`, then emits
56
+ one informational output line for copy evidence. Time complexity O(n) where
57
+ n is auth file size.
58
+ @param source_path {Path} Existing auth file source path.
59
+ @param destination_path {Path} Auth file destination path to overwrite.
60
+ @param direction_label {str} Copy direction descriptor shown in info output.
61
+ @return {None} Applies destination filesystem mutation.
62
+ @throws {OSError} If source read, destination unlink, or copy operation fails.
63
+ @satisfies REQ-043, REQ-044
64
+ """
65
+ destination_path.parent.mkdir(parents=True, exist_ok=True)
66
+ if destination_path.exists() or destination_path.is_symlink():
67
+ destination_path.unlink()
68
+ shutil.copy2(source_path, destination_path)
69
+ print_info(
70
+ f"Copied auth.json ({direction_label}): {source_path} -> {destination_path}"
71
+ )
72
+
73
+
74
+ def run(args: list[str]) -> int:
75
+ """@brief Launch Codex CLI with project-scoped environment preparation.
76
+
77
+ @details Resolves project root, copies auth from home into project auth
78
+ file, emits copy evidence output, sets `CODEX_HOME=<project-root>/.codex`,
79
+ executes `codex --yolo` plus pass-through args through blocking subprocess
80
+ run, then copies project auth back to home path in a `finally` block with
81
+ reverse-direction copy evidence output.
82
+ @param args {list[str]} Additional CLI args forwarded to Codex.
83
+ @return {int} Child process return code.
84
+ @throws {OSError} Propagated for auth-file copy or process-launch failures.
85
+ @satisfies REQ-014, REQ-043, REQ-044, REQ-064
86
+ """
87
+ project_root = require_project_root()
88
+ project_auth_path = project_root / ".codex" / "auth.json"
89
+ home_auth_path = Path.home() / ".codex" / "auth.json"
90
+ _copy_auth_file(
91
+ home_auth_path,
92
+ project_auth_path,
93
+ "global config -> project config",
94
+ )
95
+ codex_home = str(project_root / ".codex")
96
+ os.environ["CODEX_HOME"] = codex_home
97
+ cmd = ["codex", "--yolo"] + args
98
+ cmd[0] = require_commands(cmd[0])
99
+ try:
100
+ result = subprocess.run(cmd)
101
+ finally:
102
+ _copy_auth_file(
103
+ project_auth_path,
104
+ home_auth_path,
105
+ "project config -> global config",
106
+ )
107
+ return result.returncode
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python3
2
+ """@brief GitHub Copilot CLI launcher in project context.
3
+
4
+ @details Ensures project-root prerequisite and executable availability before
5
+ blocking subprocess invocation of `copilot`.
6
+ @satisfies REQ-015, REQ-055, REQ-056, REQ-064
7
+ """
8
+
9
+ import subprocess
10
+
11
+ from shell_scripts.utils import require_project_root, require_commands
12
+
13
+ ## @var PROGRAM
14
+ # @brief Base CLI program name used in help output.
15
+ # @details Constant identifier for usage-line rendering in command help.
16
+ PROGRAM = "shellscripts"
17
+ ## @var DESCRIPTION
18
+ # @brief One-line command description for dispatcher help surfaces.
19
+ # @details Exposed by command registry introspection (`get_all_commands`).
20
+ DESCRIPTION = "Launch GitHub Copilot CLI in the project context."
21
+
22
+
23
+ def print_help(version):
24
+ """@brief Print command-specific help for `copilot`.
25
+
26
+ @details Emits usage and pass-through argument behavior for deterministic
27
+ terminal rendering; does not mutate process state.
28
+ @param version {str} CLI version string propagated by dispatcher.
29
+ @return {None} Writes help text to stdout.
30
+ @satisfies DES-008
31
+ """
32
+
33
+ print(f"Usage: {PROGRAM} copilot [args...] ({version})")
34
+ print()
35
+ print("copilot options:")
36
+ print(" All arguments are passed through to the Copilot CLI.")
37
+ print(" --help - Show this help message.")
38
+
39
+
40
+ def run(args):
41
+ """@brief Launch Copilot CLI after external executable validation.
42
+
43
+ @details Resolves project root, checks executable availability for
44
+ `copilot`, then executes pass-through args through blocking subprocess run.
45
+ @param args {list[str]} Additional CLI args forwarded to Copilot.
46
+ @return {int} Child process return code.
47
+ @satisfies REQ-015, REQ-055, REQ-056, REQ-064
48
+ """
49
+
50
+ require_project_root()
51
+ cmd = ["copilot", "--yolo", "--allow-all-tools", "--no-auto-update"] + args
52
+ cmd[0] = require_commands(cmd[0])
53
+ result = subprocess.run(cmd)
54
+ return result.returncode
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """@brief PixelMed DICOM-to-JPEG converter launcher.
3
+
4
+ @details Resolves Java runtime and classpath prerequisites and validates Java
5
+ executable availability before subprocess invocation.
6
+ @satisfies REQ-026, REQ-055, REQ-056
7
+ """
8
+
9
+ import os
10
+ import subprocess
11
+ import shutil
12
+
13
+ from shell_scripts.utils import print_error, require_commands
14
+
15
+ PROGRAM = "shellscripts"
16
+ DESCRIPTION = "Convert DICOM images to JPEG using PixelMed."
17
+
18
+ JAVA_WRAPPERS = "/usr/lib/java-wrappers/java-wrappers.sh"
19
+
20
+
21
+ def print_help(version):
22
+ print(f"Usage: {PROGRAM} dicom2jpg <input.dcm> <output.jpg> ({version})")
23
+ print()
24
+ print("dicom2jpg options:")
25
+ print(" <input.dcm> - Input DICOM file (required).")
26
+ print(" <output.jpg> - Output JPEG file (required).")
27
+ print(" --help - Show this help message.")
28
+
29
+
30
+ def _find_java():
31
+ for java in ("java", "/usr/bin/java"):
32
+ if shutil.which(java):
33
+ return java
34
+ return None
35
+
36
+
37
+ def _find_jars(*jar_names):
38
+ jar_dirs = ["/usr/share/java", "/usr/local/share/java"]
39
+ found = []
40
+ for name in jar_names:
41
+ for d in jar_dirs:
42
+ jar_path = os.path.join(d, f"{name}.jar")
43
+ if os.path.exists(jar_path):
44
+ found.append(jar_path)
45
+ break
46
+ return found
47
+
48
+
49
+ def run(args):
50
+ """@brief Run PixelMed converter after executable validation.
51
+
52
+ @details Validates Java runtime availability and command executability for
53
+ the selected Java binary before invoking conversion class.
54
+ @param args {list[str]} Expected `[input_dicom, output_jpeg]` arguments.
55
+ @return {int} Subprocess return code.
56
+ @satisfies REQ-026, REQ-055, REQ-056
57
+ """
58
+
59
+ if len(args) < 2:
60
+ print_error("Usage: dicom2jpg <input.dcm> <output.jpg>")
61
+ return 1
62
+
63
+ input_file = args[0]
64
+ output_file = args[1]
65
+
66
+ if not os.path.exists(JAVA_WRAPPERS):
67
+ print_error("java-wrappers not found. Install the 'java-wrappers' package.")
68
+ return 1
69
+
70
+ java = _find_java()
71
+ if not java:
72
+ print_error("Java runtime not found.")
73
+ return 1
74
+
75
+ jars = _find_jars(
76
+ "pixelmed",
77
+ "hsqldb",
78
+ "vecmath",
79
+ "jmdns",
80
+ "commons-codec",
81
+ "jai_imageio",
82
+ "clibwrapper_jiio",
83
+ )
84
+ classpath = ":".join(jars) if jars else ""
85
+
86
+ cmd = [
87
+ java,
88
+ "-Djava.awt.headless=true",
89
+ "-cp",
90
+ classpath,
91
+ "com.pixelmed.display.ConsumerFormatImageMaker",
92
+ input_file,
93
+ output_file,
94
+ ]
95
+
96
+ require_commands(cmd[0])
97
+ result = subprocess.run(cmd)
98
+ return result.returncode
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env python3
2
+ """@brief PixelMed DICOM viewer launcher.
3
+
4
+ @details Resolves Java runtime and classpath prerequisites and validates Java
5
+ executable availability before subprocess invocation.
6
+ @satisfies REQ-025, REQ-055, REQ-056
7
+ """
8
+
9
+ import os
10
+ import subprocess
11
+ import shutil
12
+
13
+ from shell_scripts.utils import print_error, require_commands
14
+
15
+ PROGRAM = "shellscripts"
16
+ DESCRIPTION = "Launch PixelMed DICOM image viewer."
17
+
18
+ JAVA_WRAPPERS = "/usr/lib/java-wrappers/java-wrappers.sh"
19
+
20
+
21
+ def print_help(version):
22
+ print(f"Usage: {PROGRAM} dicomviewer [files...] ({version})")
23
+ print()
24
+ print("dicomviewer options:")
25
+ print(" [files...] - DICOM files to view.")
26
+ print(" --help - Show this help message.")
27
+
28
+
29
+ def _find_java():
30
+ for java in ("java", "/usr/bin/java"):
31
+ if shutil.which(java):
32
+ return java
33
+ return None
34
+
35
+
36
+ def _find_jars(*jar_names):
37
+ jar_dirs = ["/usr/share/java", "/usr/local/share/java"]
38
+ found = []
39
+ for name in jar_names:
40
+ for d in jar_dirs:
41
+ jar_path = os.path.join(d, f"{name}.jar")
42
+ if os.path.exists(jar_path):
43
+ found.append(jar_path)
44
+ break
45
+ return found
46
+
47
+
48
+ def run(args):
49
+ """@brief Run PixelMed DICOM viewer after executable validation.
50
+
51
+ @details Validates Java runtime availability and command executability for
52
+ the selected Java binary before invoking the viewer class.
53
+ @param args {list[str]} DICOM file arguments forwarded to PixelMed viewer.
54
+ @return {int} Subprocess return code.
55
+ @satisfies REQ-025, REQ-055, REQ-056
56
+ """
57
+
58
+ if not os.path.exists(JAVA_WRAPPERS):
59
+ print_error(
60
+ "java-wrappers not found. Install the 'java-wrappers' package."
61
+ )
62
+ return 1
63
+
64
+ java = _find_java()
65
+ if not java:
66
+ print_error("Java runtime not found.")
67
+ return 1
68
+
69
+ jars = _find_jars(
70
+ "pixelmed", "hsqldb", "vecmath", "jmdns",
71
+ "commons-codec", "jai_imageio", "clibwrapper_jiio"
72
+ )
73
+ classpath = ":".join(jars) if jars else ""
74
+
75
+ cmd = [
76
+ java, "-Djava.awt.headless=true",
77
+ "-cp", classpath,
78
+ "com.pixelmed.display.DicomImageViewer",
79
+ ] + args
80
+
81
+ require_commands(cmd[0])
82
+ result = subprocess.run(cmd)
83
+ return result.returncode
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ """@brief MIME-based diff command wrapper.
3
+
4
+ @details Exposes the `diff` CLI command and delegates category-based executable
5
+ selection to shared `_dc_common.dispatch` logic.
6
+ @satisfies PRJ-003, DES-007, REQ-023, REQ-024
7
+ """
8
+
9
+ import sys
10
+
11
+ from shell_scripts.config import get_dispatch_profile
12
+ from shell_scripts.commands._dc_common import dispatch
13
+
14
+ PROGRAM = "shellscripts"
15
+ DESCRIPTION = "File differ dispatcher by MIME type."
16
+
17
+
18
+ def print_help(version):
19
+ """@brief Render command help for `diff`.
20
+
21
+ @details Prints usage, required file argument semantics, and argument
22
+ forwarding contract for the selected external diff executable.
23
+ @param version {str} Version string appended in usage output.
24
+ @return {None} Writes help text to stdout.
25
+ @satisfies DES-008
26
+ """
27
+ print(f"Usage: {PROGRAM} diff <file> [args...] ({version})")
28
+ print()
29
+ print("diff options:")
30
+ print(" <file> - File to diff (required).")
31
+ print(" Additional arguments are passed to the selected diff tool.")
32
+ print(" --help - Show this help message.")
33
+
34
+
35
+ def run(args):
36
+ """@brief Execute MIME-routed diff dispatch.
37
+
38
+ @details Validates that a file argument exists; on missing argument prints
39
+ error plus help and returns status code `2`; otherwise resolves runtime
40
+ dispatch profile and delegates by file category through shared `_dc_common`.
41
+ @param args {list[str]} CLI args where `args[0]` is file path.
42
+ @return {int} Return code `2` on missing file; otherwise delegated dispatch result.
43
+ @satisfies REQ-023, REQ-024
44
+ """
45
+ if not args:
46
+ print("Error: file argument required.", file=sys.stderr)
47
+ print_help("")
48
+ return 2
49
+ category_cmds, fallback = get_dispatch_profile("diff")
50
+ return dispatch(category_cmds, fallback, args[0], args[1:])