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,243 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import re
4
+ import subprocess
5
+ import tempfile
6
+
7
+ from shell_scripts.utils import require_commands, print_error
8
+
9
+ PROGRAM = "shellscripts"
10
+ DESCRIPTION = "Split PDF into parts by page format changes."
11
+
12
+
13
+ def print_help(version):
14
+ print(
15
+ f"Usage: {PROGRAM} pdf-split-by-format <file1.pdf> [file2.pdf ...] ({version})"
16
+ )
17
+ print()
18
+ print("pdf-split-by-format options:")
19
+ print(" <files...> - Input PDF files to split.")
20
+ print(" --help - Show this help message.")
21
+
22
+
23
+ def _get_page_formats(pdf, total_pages):
24
+ r = subprocess.run(
25
+ ["pdfinfo", "-f", "1", "-l", str(total_pages), pdf],
26
+ capture_output=True,
27
+ text=True,
28
+ stderr=subprocess.DEVNULL,
29
+ )
30
+ formats = []
31
+ for line in r.stdout.splitlines():
32
+ if re.search(r"Page\s+.*size:", line):
33
+ fmt = re.sub(r"Page\s+\d+\s+size:\s*", "", line).strip()
34
+ formats.append(fmt)
35
+ return formats
36
+
37
+
38
+ def _get_total_pages(pdf):
39
+ r = subprocess.run(
40
+ ["pdfinfo", pdf],
41
+ capture_output=True,
42
+ text=True,
43
+ stderr=subprocess.DEVNULL,
44
+ )
45
+ for line in r.stdout.splitlines():
46
+ if line.startswith("Pages:"):
47
+ return int(line.split(":")[1].strip())
48
+ return None
49
+
50
+
51
+ def _has_toc(pdf):
52
+ try:
53
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=True) as tmp:
54
+ tmp_path = tmp.name
55
+ subprocess.run(
56
+ ["qpdf", "--object-streams=disable", pdf, tmp_path],
57
+ capture_output=True,
58
+ )
59
+ r = subprocess.run(
60
+ ["pdftk", tmp_path, "dump_data"],
61
+ capture_output=True,
62
+ text=True,
63
+ )
64
+ os.unlink(tmp_path)
65
+ return "BookmarkBegin" in r.stdout
66
+ except Exception:
67
+ return False
68
+
69
+
70
+ def _extract_toc_for_range(data_file, start, end):
71
+ entries = []
72
+ with open(data_file) as f:
73
+ content = f.read()
74
+
75
+ level = title = page = ""
76
+ for line in content.splitlines():
77
+ if line == "BookmarkBegin":
78
+ if level and page:
79
+ p = int(page)
80
+ if start <= p <= end:
81
+ entries.append((level, title, str(p - start + 1)))
82
+ level = title = page = ""
83
+ elif line.startswith("BookmarkTitle: "):
84
+ title = line[15:]
85
+ elif line.startswith("BookmarkLevel: "):
86
+ level = line.split()[1]
87
+ elif line.startswith("BookmarkPageNumber: "):
88
+ page = line.split()[1]
89
+ if level and page:
90
+ p = int(page)
91
+ if start <= p <= end:
92
+ entries.append((level, title, str(p - start + 1)))
93
+
94
+ result = []
95
+ for lv, ti, pg in entries:
96
+ result.append("BookmarkBegin")
97
+ if ti:
98
+ result.append(f"BookmarkTitle: {ti}")
99
+ if lv:
100
+ result.append(f"BookmarkLevel: {lv}")
101
+ result.append(f"BookmarkPageNumber: {pg}")
102
+ return "\n".join(result)
103
+
104
+
105
+ def _apply_toc(output_file, toc_content):
106
+ if not toc_content.strip():
107
+ return
108
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".info", delete=False) as f:
109
+ f.write(toc_content)
110
+ info_file = f.name
111
+ try:
112
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as stripped_f:
113
+ stripped = stripped_f.name
114
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as toc_f:
115
+ with_toc = toc_f.name
116
+
117
+ r1 = subprocess.run(
118
+ ["qpdf", "--empty", "--pages", output_file, "1-z", "--", stripped],
119
+ capture_output=True,
120
+ )
121
+ if r1.returncode == 0:
122
+ r2 = subprocess.run(
123
+ ["pdftk", stripped, "update_info", info_file, "output", with_toc],
124
+ capture_output=True,
125
+ )
126
+ if r2.returncode == 0:
127
+ os.replace(with_toc, output_file)
128
+ else:
129
+ os.unlink(with_toc)
130
+ print(f" [!] Warning: unable to update TOC for '{output_file}'.")
131
+ os.unlink(stripped)
132
+ except Exception:
133
+ pass
134
+ finally:
135
+ os.unlink(info_file)
136
+
137
+
138
+ def run(args):
139
+ if not args:
140
+ print_error("No PDF files specified.")
141
+ print_help("")
142
+ return 1
143
+
144
+ require_commands("qpdf", "pdfinfo", "pdftk")
145
+
146
+ for input_pdf in args:
147
+ print("=" * 50)
148
+
149
+ if not os.path.isfile(input_pdf):
150
+ print(f"Warning: File '{input_pdf}' does not exist. Skipping...")
151
+ continue
152
+
153
+ print(f"Processing: {input_pdf}")
154
+ basename = os.path.splitext(os.path.basename(input_pdf))[0]
155
+
156
+ total_pages = _get_total_pages(input_pdf)
157
+ if total_pages is None:
158
+ print(f"Error: Cannot read pages from '{input_pdf}'. Skipping...")
159
+ continue
160
+
161
+ print(f"Analyzing {total_pages} pages...")
162
+ formats = _get_page_formats(input_pdf, total_pages)
163
+
164
+ unique = set(formats)
165
+ if len(unique) <= 1:
166
+ print(
167
+ f"-> INFO: All pages have the same format: [{formats[0] if formats else 'unknown'}]."
168
+ )
169
+ print("-> No splitting needed. Moving to next...")
170
+ continue
171
+
172
+ has_toc = _has_toc(input_pdf)
173
+ data_file = None
174
+ if has_toc:
175
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
176
+ tmp_pdf = tmp.name
177
+ subprocess.run(
178
+ ["qpdf", "--object-streams=disable", input_pdf, tmp_pdf],
179
+ capture_output=True,
180
+ )
181
+ data_file = tempfile.mktemp()
182
+ subprocess.run(
183
+ ["pdftk", tmp_pdf, "dump_data", "output", data_file],
184
+ capture_output=True,
185
+ )
186
+ os.unlink(tmp_pdf)
187
+
188
+ start_page = 1
189
+ chunk_num = 1
190
+ prev_format = formats[0]
191
+
192
+ for i in range(1, total_pages):
193
+ current_format = formats[i]
194
+ if current_format != prev_format:
195
+ end_page = i
196
+ out_name = f"{basename}_{chunk_num:02d}.pdf"
197
+ print(
198
+ f"-> Generating: {out_name} (Pages {start_page} - {end_page}) [Format: {prev_format}]"
199
+ )
200
+ subprocess.run(
201
+ [
202
+ "qpdf",
203
+ input_pdf,
204
+ "--pages",
205
+ ".",
206
+ f"{start_page}-{end_page}",
207
+ "--",
208
+ out_name,
209
+ ],
210
+ )
211
+ if has_toc and data_file:
212
+ toc = _extract_toc_for_range(data_file, start_page, end_page)
213
+ _apply_toc(out_name, toc)
214
+
215
+ start_page = i + 1
216
+ prev_format = current_format
217
+ chunk_num += 1
218
+
219
+ out_name = f"{basename}_{chunk_num:02d}.pdf"
220
+ print(
221
+ f"-> Generating: {out_name} (Pages {start_page} - {total_pages}) [Format: {prev_format}]"
222
+ )
223
+ subprocess.run(
224
+ [
225
+ "qpdf",
226
+ input_pdf,
227
+ "--pages",
228
+ ".",
229
+ f"{start_page}-{total_pages}",
230
+ "--",
231
+ out_name,
232
+ ],
233
+ )
234
+ if has_toc and data_file:
235
+ toc = _extract_toc_for_range(data_file, start_page, total_pages)
236
+ _apply_toc(out_name, toc)
237
+ os.unlink(data_file)
238
+
239
+ print(f"-> Splitting of '{input_pdf}' complete: generated {chunk_num} files.")
240
+
241
+ print("=" * 50)
242
+ print("Batch processing completed successfully!")
243
+ return 0
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import re
4
+ import subprocess
5
+ import tempfile
6
+
7
+ from shell_scripts.utils import require_commands, print_error
8
+
9
+ PROGRAM = "shellscripts"
10
+ DESCRIPTION = "Split PDF into chapters by TOC level-1 entries."
11
+
12
+
13
+ def print_help(version):
14
+ print(f"Usage: {PROGRAM} pdf-split-by-toc <document.pdf> ({version})")
15
+ print()
16
+ print("pdf-split-by-toc options:")
17
+ print(" <document.pdf> - Input PDF file (required).")
18
+ print(" --help - Show this help message.")
19
+
20
+
21
+ def _parse_level1_toc(dump_content):
22
+ entries = []
23
+ level = title = page = ""
24
+ for line in dump_content.splitlines():
25
+ if line == "BookmarkBegin":
26
+ if level == "1" and page:
27
+ entries.append((int(page), title))
28
+ level = title = page = ""
29
+ elif line.startswith("BookmarkTitle: "):
30
+ title = line[15:]
31
+ elif line.startswith("BookmarkLevel: "):
32
+ level = line.split()[1]
33
+ elif line.startswith("BookmarkPageNumber: "):
34
+ page = line.split()[1]
35
+ if level == "1" and page:
36
+ entries.append((int(page), title))
37
+ return entries
38
+
39
+
40
+ def _extract_toc_for_range(dump_content, start, end):
41
+ lines = []
42
+ level = title = page = ""
43
+ for line in dump_content.splitlines():
44
+ if line == "BookmarkBegin":
45
+ if level and page:
46
+ p = int(page)
47
+ if start <= p <= end:
48
+ lines.append("BookmarkBegin")
49
+ if title:
50
+ lines.append(f"BookmarkTitle: {title}")
51
+ if level:
52
+ lines.append(f"BookmarkLevel: {level}")
53
+ lines.append(f"BookmarkPageNumber: {p - start + 1}")
54
+ level = title = page = ""
55
+ elif line.startswith("BookmarkTitle: "):
56
+ title = line[15:]
57
+ elif line.startswith("BookmarkLevel: "):
58
+ level = line.split()[1]
59
+ elif line.startswith("BookmarkPageNumber: "):
60
+ page = line.split()[1]
61
+ if level and page:
62
+ p = int(page)
63
+ if start <= p <= end:
64
+ lines.append("BookmarkBegin")
65
+ if title:
66
+ lines.append(f"BookmarkTitle: {title}")
67
+ if level:
68
+ lines.append(f"BookmarkLevel: {level}")
69
+ lines.append(f"BookmarkPageNumber: {p - start + 1}")
70
+ return "\n".join(lines)
71
+
72
+
73
+ def _sanitize_title(title):
74
+ safe = re.sub(r"[^A-Za-z0-9]", "-", title)
75
+ safe = re.sub(r"-+", "-", safe)
76
+ return safe.strip("-")
77
+
78
+
79
+ def _apply_toc_to_file(output_file, toc_content):
80
+ if not toc_content.strip():
81
+ return
82
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".info", delete=False) as f:
83
+ f.write(toc_content)
84
+ info_file = f.name
85
+ try:
86
+ stripped = tempfile.mktemp(suffix=".pdf")
87
+ with_toc = tempfile.mktemp(suffix=".pdf")
88
+ r1 = subprocess.run(
89
+ ["qpdf", "--empty", "--pages", output_file, "1-z", "--", stripped],
90
+ capture_output=True,
91
+ )
92
+ if r1.returncode == 0:
93
+ r2 = subprocess.run(
94
+ ["pdftk", stripped, "update_info", info_file, "output", with_toc],
95
+ capture_output=True,
96
+ )
97
+ if r2.returncode == 0:
98
+ os.replace(with_toc, output_file)
99
+ else:
100
+ if os.path.exists(with_toc):
101
+ os.unlink(with_toc)
102
+ print(f" [!] Warning: unable to update TOC for '{output_file}'.")
103
+ if os.path.exists(stripped):
104
+ os.unlink(stripped)
105
+ finally:
106
+ os.unlink(info_file)
107
+
108
+
109
+ def run(args):
110
+ if not args or args[0].startswith("-"):
111
+ print_error("Input PDF file required.")
112
+ print_help("")
113
+ return 1
114
+
115
+ require_commands("pdftk", "qpdf")
116
+
117
+ input_file = args[0]
118
+ if not os.path.isfile(input_file):
119
+ print_error(f"File not found: {input_file}")
120
+ return 1
121
+
122
+ basename = os.path.splitext(os.path.basename(input_file))[0]
123
+
124
+ with tempfile.TemporaryDirectory() as tmp_dir:
125
+ tmp_pdf = os.path.join(tmp_dir, "prepared.pdf")
126
+ data_file = os.path.join(tmp_dir, "data.txt")
127
+
128
+ print("Preparing document (disabling Object Streams for TOC reading)...")
129
+ r = subprocess.run(
130
+ ["qpdf", "--object-streams=disable", input_file, tmp_pdf],
131
+ capture_output=True,
132
+ )
133
+ if r.returncode != 0:
134
+ print_error("Error during initial processing with qpdf.")
135
+ return 1
136
+
137
+ print("Extracting TOC data...")
138
+ subprocess.run(
139
+ ["pdftk", tmp_pdf, "dump_data", "output", data_file],
140
+ capture_output=True,
141
+ )
142
+
143
+ with open(data_file) as f:
144
+ dump_content = f.read()
145
+
146
+ if "BookmarkBegin" not in dump_content:
147
+ print_error("No TOC found even after decompression.")
148
+ print("The PDF may use 'Named Destinations' instead of explicit page numbers.")
149
+ return 1
150
+
151
+ total_pages = None
152
+ for line in dump_content.splitlines():
153
+ if line.startswith("NumberOfPages:"):
154
+ total_pages = int(line.split()[1])
155
+ break
156
+
157
+ if total_pages is None:
158
+ print_error("Cannot read total pages.")
159
+ return 1
160
+
161
+ print(f"Document has {total_pages} total pages.")
162
+
163
+ toc_entries = _parse_level1_toc(dump_content)
164
+ if not toc_entries:
165
+ print_error("TOC found, but no valid level-1 entries with explicit pages.")
166
+ return 1
167
+
168
+ num_sections = len(toc_entries)
169
+ print(f"Found {num_sections} level-1 chapters. Starting split...")
170
+
171
+ for i, (start_page, title) in enumerate(toc_entries):
172
+ if i + 1 < num_sections:
173
+ next_page = toc_entries[i + 1][0]
174
+ end_page = max(next_page - 1, start_page)
175
+ else:
176
+ end_page = total_pages
177
+
178
+ safe_title = _sanitize_title(title)
179
+ index_padded = f"{i + 1:02d}"
180
+ output_name = f"{basename}_{index_padded}_{safe_title}.pdf"
181
+
182
+ print(f"-> Creating: {output_name} (Pages {start_page} - {end_page})")
183
+
184
+ r = subprocess.run(
185
+ ["qpdf", input_file, "--pages", input_file,
186
+ f"{start_page}-{end_page}", "--", output_name],
187
+ )
188
+ if r.returncode != 0:
189
+ print(f" [!] Error extracting section '{title}'")
190
+ continue
191
+
192
+ toc_content = _extract_toc_for_range(dump_content, start_page, end_page)
193
+ _apply_toc_to_file(output_name, toc_content)
194
+
195
+ print("---")
196
+ print("Split completed successfully!")
197
+ return 0
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from shell_scripts.utils import require_commands, print_error
6
+
7
+ PROGRAM = "shellscripts"
8
+ DESCRIPTION = "Tile PDF to A4 pages at 90% scale using plakativ."
9
+
10
+
11
+ def print_help(version):
12
+ print(f"Usage: {PROGRAM} pdf-tiler-090 <input.pdf> ({version})")
13
+ print()
14
+ print("pdf-tiler-090 options:")
15
+ print(" <input.pdf> - Input PDF file (required).")
16
+ print(" --help - Show this help message.")
17
+ print()
18
+ print("Outputs <basename>_tiled-A4.pdf in the same directory as the input.")
19
+
20
+
21
+ def run(args):
22
+ """@brief Execute `pdf-tiler-090` command with blocking subprocess launch.
23
+
24
+ @details Validates input argument and file presence, builds `plakativ`
25
+ command vector with fixed A4/0.90 parameters, then executes it via
26
+ `subprocess.run` while inheriting stdio streams.
27
+ @param args {list[str]} Command arguments excluding command token.
28
+ @return {int} `1` on validation failure; child process return code otherwise.
29
+ @satisfies REQ-029, REQ-055, REQ-056, REQ-064
30
+ """
31
+
32
+ if not args or args[0].startswith("-"):
33
+ print_error("Input PDF file required.")
34
+ print_help("")
35
+ return 1
36
+
37
+ require_commands("plakativ")
38
+
39
+ input_file = Path(args[0])
40
+ if not input_file.is_file():
41
+ print_error(f"File not found: {input_file}")
42
+ return 1
43
+
44
+ output_file = input_file.parent / f"{input_file.stem}_tiled-A4.pdf"
45
+
46
+ cmd = [
47
+ "plakativ",
48
+ "--factor",
49
+ "0.90",
50
+ "--pagesize",
51
+ "A4",
52
+ "--border",
53
+ "5mm",
54
+ "--cutting-guides",
55
+ "-o",
56
+ str(output_file),
57
+ str(input_file),
58
+ ]
59
+ require_commands(cmd[0])
60
+ result = subprocess.run(cmd)
61
+ return result.returncode
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from shell_scripts.utils import require_commands, print_error
6
+
7
+ PROGRAM = "shellscripts"
8
+ DESCRIPTION = "Tile PDF to A4 pages at original A1 size using plakativ."
9
+
10
+
11
+ def print_help(version):
12
+ print(f"Usage: {PROGRAM} pdf-tiler-100 <input.pdf> ({version})")
13
+ print()
14
+ print("pdf-tiler-100 options:")
15
+ print(" <input.pdf> - Input PDF file (required).")
16
+ print(" --help - Show this help message.")
17
+ print()
18
+ print("Outputs <basename>_tiled-A4.pdf in the same directory as the input.")
19
+
20
+
21
+ def run(args):
22
+ """@brief Execute `pdf-tiler-100` command with blocking subprocess launch.
23
+
24
+ @details Validates input argument and file presence, builds `plakativ`
25
+ command vector with fixed A1 source size and A4 tiling parameters, then
26
+ executes it via `subprocess.run` while inheriting stdio streams.
27
+ @param args {list[str]} Command arguments excluding command token.
28
+ @return {int} `1` on validation failure; child process return code otherwise.
29
+ @satisfies REQ-029, REQ-055, REQ-056, REQ-064
30
+ """
31
+
32
+ if not args or args[0].startswith("-"):
33
+ print_error("Input PDF file required.")
34
+ print_help("")
35
+ return 1
36
+
37
+ require_commands("plakativ")
38
+
39
+ input_file = Path(args[0])
40
+ if not input_file.is_file():
41
+ print_error(f"File not found: {input_file}")
42
+ return 1
43
+
44
+ output_file = input_file.parent / f"{input_file.stem}_tiled-A4.pdf"
45
+
46
+ cmd = [
47
+ "plakativ",
48
+ "--size",
49
+ "A1",
50
+ "--pagesize",
51
+ "A4",
52
+ "--border",
53
+ "5mm",
54
+ "--cutting-guides",
55
+ "-o",
56
+ str(output_file),
57
+ str(input_file),
58
+ ]
59
+ require_commands(cmd[0])
60
+ result = subprocess.run(cmd)
61
+ return result.returncode