sbo-create 2.2.0__tar.gz → 2.3.0__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.
Files changed (22) hide show
  1. {sbo_create-2.2.0 → sbo_create-2.3.0}/PKG-INFO +6 -5
  2. {sbo_create-2.2.0 → sbo_create-2.3.0}/README.md +5 -4
  3. {sbo_create-2.2.0 → sbo_create-2.3.0}/pyproject.toml +1 -1
  4. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/__metadata__.py +1 -1
  5. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/cli.py +52 -24
  6. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/bump.py +14 -7
  7. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/git.py +72 -5
  8. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/misc.py +10 -3
  9. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/main.py +11 -5
  10. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/models.py +95 -59
  11. {sbo_create-2.2.0 → sbo_create-2.3.0}/LICENSE +0 -0
  12. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/__init__.py +0 -0
  13. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/__init__.py +0 -0
  14. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/download.py +0 -0
  15. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/fallback.py +0 -0
  16. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/fetch.py +0 -0
  17. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/files.py +0 -0
  18. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/fix.py +0 -0
  19. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/repology.py +0 -0
  20. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/scan.py +0 -0
  21. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/validate.py +0 -0
  22. {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/template_loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sbo-create
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: SBo tool for creating SlackBuilds.
5
5
  Keywords: sbo,templates,slackbuild
6
6
  Author-email: Dimitris Zlatanidis <dslackw@gmail.com>
@@ -76,10 +76,11 @@ bumping versions, and packaging for submission — all from a single tool.
76
76
  | `fetch` | Fetch a SlackBuild package from SBo or local clone |
77
77
  | `deps` | Show dependency tree for a package |
78
78
  | `rdeps` | Show reverse dependencies for a package |
79
- | `diff` | Show diff between local files and the SBo repository |
80
- | `copy` | Copy local package files to the SBo repository |
81
- | `sync` | Sync the local SBo git repository |
82
- | `submit` | Commit and push a package update via git |
79
+ | `repo diff` | Show diff between local files and the SBo repository |
80
+ | `repo sync` | Sync the local SBo git repository |
81
+ | `repo submit` | Commit and push a package update via git |
82
+ | `repo copy` | Copy local package files to the SBo repository |
83
+ | `repo log` | Show packages committed on current branch |
83
84
  | `repology` | Check package versions via repology.org |
84
85
  | `clean` | Remove downloaded source tarballs |
85
86
  | `maintainer` | Create or update maintainer configuration |
@@ -36,10 +36,11 @@ bumping versions, and packaging for submission — all from a single tool.
36
36
  | `fetch` | Fetch a SlackBuild package from SBo or local clone |
37
37
  | `deps` | Show dependency tree for a package |
38
38
  | `rdeps` | Show reverse dependencies for a package |
39
- | `diff` | Show diff between local files and the SBo repository |
40
- | `copy` | Copy local package files to the SBo repository |
41
- | `sync` | Sync the local SBo git repository |
42
- | `submit` | Commit and push a package update via git |
39
+ | `repo diff` | Show diff between local files and the SBo repository |
40
+ | `repo sync` | Sync the local SBo git repository |
41
+ | `repo submit` | Commit and push a package update via git |
42
+ | `repo copy` | Copy local package files to the SBo repository |
43
+ | `repo log` | Show packages committed on current branch |
43
44
  | `repology` | Check package versions via repology.org |
44
45
  | `clean` | Remove downloaded source tarballs |
45
46
  | `maintainer` | Create or update maintainer configuration |
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "sbo-create"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  authors = [
9
9
  { name = "Dimitris Zlatanidis", email = "dslackw@gmail.com" },
10
10
  ]
@@ -4,7 +4,7 @@
4
4
  __prog__: str = 'sbo-create'
5
5
  __author__: str = 'dslackw'
6
6
  __copyright__: str = '2015-2026'
7
- __version_info__: tuple[int, int, int] = (2, 2, 0)
7
+ __version_info__: tuple[int, int, int] = (2, 3, 0)
8
8
  __version__: str = f'{__version_info__[0]}.{__version_info__[1]}.{__version_info__[2]}'
9
9
  __license__: str = 'GNU General Public License v3 (GPLv3)'
10
10
  __email__: str = 'dslackw@gmail.com'
@@ -60,7 +60,7 @@ class _SBoCreateParser(argparse.ArgumentParser):
60
60
  if 'invalid choice' in message and 'argument -' not in message:
61
61
  cmd: str = message.split("'")[1] if "'" in message else message
62
62
  self.exit(2, f"{PROG}: Unknown command '{cmd}'.\n"
63
- f"Use '{PROG} --help' for available commands.\n")
63
+ f"Use '{PROG} --help' for available commands.\n")
64
64
  super().error(message)
65
65
 
66
66
 
@@ -236,11 +236,6 @@ def parse_args(version: str) -> argparse.Namespace: # pylint: disable=too-many-
236
236
  sp.add_argument('-W', '--wide', action='store_true',
237
237
  help='Show maintainer packages in multi-column layout.')
238
238
 
239
- # copy
240
- sp = sub.add_parser('copy', **_kw,
241
- help='Copy local package files to the SBo repository.')
242
- sp.set_defaults(command='copy')
243
-
244
239
  # deps
245
240
  sp = sub.add_parser('deps', **_kw,
246
241
  help='Show the full dependency tree for a package.')
@@ -257,24 +252,53 @@ def parse_args(version: str) -> argparse.Namespace: # pylint: disable=too-many-
257
252
  sp.add_argument('-t', '--tree', action='store_true',
258
253
  help='Show full recursive reverse tree instead of flat list.')
259
254
 
260
- # diff
261
- sp = sub.add_parser('diff', **_kw,
262
- help='Show diff between local files and the SBo repository.')
263
- sp.set_defaults(command='diff')
264
- sp.add_argument('names', metavar='NAME', nargs='*',
265
- help='Package name(s) to diff. Defaults to the current directory.')
266
- sp.add_argument('-U', '--unified', metavar='N', type=int, default=3,
267
- help='Lines of context around each change (default: 3).')
268
-
269
- # sync
270
- sp = sub.add_parser('sync', **_kw,
271
- help='Sync SBo repo: pull main branch, recreate working branch.')
272
- sp.set_defaults(command='sync')
273
-
274
- # submit
275
- sp = sub.add_parser('submit', **_kw,
276
- help='Submit package via git or open the SBo submission page.')
277
- sp.set_defaults(command='submit')
255
+ # repo (groups all local SBo repository operations)
256
+ repo_parser = _SBoCreateParser(
257
+ prog='sbo-create repo', add_help=False,
258
+ formatter_class=_ColorHelpFormatter,
259
+ epilog='For command-specific help: sbo-create repo <COMMAND> --help')
260
+ repo_options = repo_parser.add_argument_group('Options')
261
+ repo_options.add_argument('-N', '--no-color', action='store_true',
262
+ default=argparse.SUPPRESS, help=argparse.SUPPRESS)
263
+ repo_options.add_argument('-h', '--help', action='help',
264
+ help='Show this help message and exit.')
265
+ repo_sub = repo_parser.add_subparsers(dest='repo_command', metavar='COMMAND')
266
+
267
+ sp = sub.add_parser('repo',
268
+ parents=[repo_parser],
269
+ formatter_class=_ColorHelpFormatter,
270
+ add_help=False,
271
+ help='Local SBo repository operations.')
272
+ sp.set_defaults(command='repo')
273
+
274
+ # repo diff
275
+ rsp = repo_sub.add_parser('diff', **_kw,
276
+ help='Show diff between local files and the SBo repository.')
277
+ rsp.set_defaults(command='repo', repo_command='diff')
278
+ rsp.add_argument('names', metavar='NAME', nargs='*',
279
+ help='Package name(s) to diff. Defaults to the current directory.')
280
+ rsp.add_argument('-U', '--unified', metavar='N', type=int, default=3,
281
+ help='Lines of context around each change (default: 3).')
282
+
283
+ # repo sync
284
+ rsp = repo_sub.add_parser('sync', **_kw,
285
+ help='Sync SBo repo: pull main branch, recreate working branch.')
286
+ rsp.set_defaults(command='repo', repo_command='sync')
287
+
288
+ # repo submit
289
+ rsp = repo_sub.add_parser('submit', **_kw,
290
+ help='Submit package via git or open the SBo submission page.')
291
+ rsp.set_defaults(command='repo', repo_command='submit')
292
+
293
+ # repo copy
294
+ rsp = repo_sub.add_parser('copy', **_kw,
295
+ help='Copy local package files to the SBo repository.')
296
+ rsp.set_defaults(command='repo', repo_command='copy')
297
+
298
+ # repo log
299
+ rsp = repo_sub.add_parser('log', **_kw,
300
+ help='Show packages committed on current branch since last public update.')
301
+ rsp.set_defaults(command='repo', repo_command='log')
278
302
 
279
303
  # update
280
304
  sp = sub.add_parser('update', **_kw,
@@ -307,4 +331,8 @@ def parse_args(version: str) -> argparse.Namespace: # pylint: disable=too-many-
307
331
  parser.print_help()
308
332
  raise SystemExit(0)
309
333
 
334
+ if parsed.command == 'repo' and not getattr(parsed, 'repo_command', None):
335
+ repo_parser.print_help()
336
+ raise SystemExit(0)
337
+
310
338
  return parsed
@@ -85,7 +85,7 @@ class BumpHandler: # pylint: disable=too-few-public-methods
85
85
  body: str = f' \\\n{indent}'.join(values)
86
86
  return f'{label}="{body}"'
87
87
 
88
- def _bump_md5sums(self, content: str) -> str:
88
+ def _bump_md5sums(self, content: str) -> tuple[str, bool]:
89
89
  """Download sources and recompute MD5SUMs, updating the .info content.
90
90
 
91
91
  Reads current download URLs from AppContext, downloads each source,
@@ -95,7 +95,8 @@ class BumpHandler: # pylint: disable=too-few-public-methods
95
95
  content: Current .info file content as a string.
96
96
 
97
97
  Returns:
98
- Updated .info content with recomputed MD5SUM fields.
98
+ A tuple of (updated content, all_ok) where all_ok is False if any
99
+ download failed (i.e. run_downloads returned an empty MD5 string).
99
100
  """
100
101
  self.files.read_info_file()
101
102
 
@@ -113,9 +114,10 @@ class BumpHandler: # pylint: disable=too-few-public-methods
113
114
  all_links.extend(links)
114
115
 
115
116
  if not all_links:
116
- return content
117
+ return content, True
117
118
 
118
119
  all_md5s: list[str] = self.download.run_downloads(all_links, {}, verify=False)
120
+ all_ok: bool = all(all_md5s)
119
121
 
120
122
  for _attr, label, start, end in groups:
121
123
  valid_md5s: list[str] = [m for m in all_md5s[start:end] if m]
@@ -123,7 +125,7 @@ class BumpHandler: # pylint: disable=too-few-public-methods
123
125
  replacement: str = self._format_info_field(label, valid_md5s)
124
126
  content = re.sub(rf'{label}="[^"]*"', replacement, content)
125
127
 
126
- return content
128
+ return content, all_ok
127
129
 
128
130
  def bump_version(self, new_version: str, with_sources: bool = False) -> None: # pylint: disable=too-many-branches
129
131
  """Bump VERSION in .info and .SlackBuild files to the given new version.
@@ -172,10 +174,15 @@ class BumpHandler: # pylint: disable=too-few-public-methods
172
174
  if with_sources and content:
173
175
  if not confirm("Download sources and recompute checksums?"):
174
176
  raise SystemExit(0)
175
- content = self._bump_md5sums(content)
177
+ content, all_ok = self._bump_md5sums(content)
176
178
  info_file.write_text(content, encoding='utf-8')
177
- print(f" {'MD5SUM':<12} {colorize('OK', 'green')}")
178
- print(colorize(f"\nVersion bumped to '{new_version}'.", 'green'))
179
+ if all_ok:
180
+ print(f" {'MD5SUM':<12} {colorize('OK', 'green')}")
181
+ print(colorize(f"\nVersion bumped to '{new_version}'.", 'green'))
182
+ else:
183
+ print(f" {'MD5SUM':<12} {colorize('FAILED', 'red')}")
184
+ print(colorize(f"\nVersion bumped to '{new_version}', but some sources could not be downloaded.", 'yellow'))
185
+ print(colorize(" MD5SUMs for failed downloads were not updated.", 'yellow'))
179
186
  raise SystemExit(0)
180
187
 
181
188
  print()
@@ -3,6 +3,7 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import re
6
7
  import subprocess
7
8
  from pathlib import Path
8
9
 
@@ -95,6 +96,36 @@ class GitHandler:
95
96
  return ['git', 'push', '--set-upstream', 'origin', git_branch]
96
97
  return self.ctx.maintainer.git_push_cmd.split()
97
98
 
99
+ @staticmethod
100
+ def _changed_files_in_pkg(repo_path: Path, category: str, name: str) -> list[tuple[str, str]]:
101
+ """Return (status, filename) pairs for changed files in a package directory.
102
+
103
+ Runs 'git status --short' scoped to the given package directory and returns
104
+ one entry per changed file. Status is a single character: M (modified),
105
+ A (added), D (deleted), or ? (untracked).
106
+
107
+ Args:
108
+ repo_path: Path to the git repository root.
109
+ category: SBo category (e.g. 'desktop').
110
+ name: Package name (e.g. 'sun').
111
+
112
+ Returns:
113
+ List of (status, filename) tuples, ordered as git reports them.
114
+ """
115
+ result = subprocess.run(
116
+ ['git', 'status', '--short', '--', f'{category}/{name}/'],
117
+ cwd=repo_path, capture_output=True, text=True, check=False,
118
+ )
119
+ entries: list[tuple[str, str]] = []
120
+ for line in result.stdout.splitlines():
121
+ if len(line) < 4:
122
+ continue
123
+ raw_status: str = line[:2].strip() or '?'
124
+ status: str = raw_status[0] if raw_status != '??' else '?'
125
+ filename: str = Path(line[3:].strip()).name
126
+ entries.append((status, filename))
127
+ return entries
128
+
98
129
  @staticmethod
99
130
  def _detect_packages_from_git(repo_path: Path) -> list[tuple[str, str]]:
100
131
  """Return unique (name, category) pairs for all modified files in the git repo.
@@ -181,6 +212,40 @@ class GitHandler:
181
212
  print(colorize(f"\nBranch '{branch}' is ready.", 'green'))
182
213
  raise SystemExit(0)
183
214
 
215
+ def log_branch(self) -> None:
216
+ """Show packages committed on the current branch since the last public SBo update.
217
+
218
+ Runs 'git log <main>..HEAD --format=%s' and parses commit subjects of the
219
+ form 'category/prgnam: Updated for version X.Y.Z.' into a formatted table.
220
+ Unrecognised commit subjects are printed as-is.
221
+ """
222
+ repo_path: Path = self._require_repo_path()
223
+ main: str = self.ctx.maintainer.git_main_branch or 'master'
224
+ branch: str = self.ctx.maintainer.git_branch or 'current branch'
225
+
226
+ result = subprocess.run(
227
+ ['git', 'log', f'{main}..HEAD', '--format=%s'],
228
+ cwd=repo_path, capture_output=True, text=True, check=False,
229
+ )
230
+ subjects: list[str] = [s for s in result.stdout.splitlines() if s.strip()]
231
+ if not subjects:
232
+ print(f"No commits ahead of '{colorize(main, 'cyan')}'.")
233
+ raise SystemExit(0)
234
+
235
+ entries: list[tuple[str, str]] = []
236
+ for subject in subjects:
237
+ m = re.match(r'^(\S+/\S+):\s+.*?(\d[\w.\-]*)\.?\s*$', subject)
238
+ entries.append((m.group(1), m.group(2)) if m else (subject, ''))
239
+
240
+ print(f"Commits on {colorize(branch, 'cyan')} (since {main}):\n")
241
+ for pkg, ver in entries:
242
+ line: str = f" {colorize(pkg, 'yellow')}"
243
+ if ver:
244
+ line += f" {ver}"
245
+ print(line)
246
+ print(colorize(f"\n {len(entries)} package(s) updated.", 'green'))
247
+ raise SystemExit(0)
248
+
184
249
  def submit_package(self) -> None: # pylint: disable=too-many-locals,too-many-statements,too-many-branches
185
250
  """Submit the package to SBo via git commit and optional push.
186
251
 
@@ -229,14 +294,16 @@ class GitHandler:
229
294
  msg = default_msg
230
295
  entries.append((name, category, version, msg))
231
296
 
297
+ status_color: dict[str, str] = {'M': 'yellow', 'A': 'green', 'D': 'red', '?': 'cyan'}
298
+
232
299
  print("\nCommands to run:\n")
233
300
  for name, category, _, msg in entries:
234
- pkg_path: Path = repo_path / category / name
235
- if pkg_path.is_dir():
236
- files: list[str] = sorted(f.name for f in pkg_path.iterdir() if f.is_file())
301
+ changed: list[tuple[str, str]] = self._changed_files_in_pkg(repo_path, category, name)
302
+ if changed:
237
303
  print(f"Files in {category}/{name}/:")
238
- for f in files:
239
- print(f" {colorize(f, 'yellow')}")
304
+ for status, filename in changed:
305
+ color: str = status_color.get(status, 'yellow')
306
+ print(f" {colorize(status, color)} {filename}")
240
307
  print()
241
308
  print(f" {colorize(f'git add {category}/{name}/', 'cyan')}")
242
309
  commit_display: str = ' '.join(commit_base) + ' "' + msg + '"'
@@ -254,8 +254,9 @@ class MiscHandler:
254
254
 
255
255
  Optionally runs strict validation before packaging. If validation reveals
256
256
  that source files are included, offers to clean them first and re-validates.
257
- Packages the four required files (.SlackBuild, .info, slack-desc, README)
258
- into a gzip-compressed tar archive placed in the parent of current_folder.
257
+ Packages all files in current_folder except source archives (matched by
258
+ SOURCE_EXTENSIONS) into a gzip-compressed tar archive placed in the parent
259
+ of current_folder.
259
260
  """
260
261
  self.files.read_info_file()
261
262
 
@@ -279,7 +280,10 @@ class MiscHandler:
279
280
 
280
281
  name: str = self.ctx.prg_name
281
282
  dest: Path = self.ctx.current_folder.parent / f'{name}.tar.gz'
282
- files: list[str] = [f'{name}.SlackBuild', f'{name}.info', 'slack-desc', 'README']
283
+ files: list[str] = sorted(
284
+ f.name for f in self.ctx.current_folder.iterdir()
285
+ if f.is_file() and not f.name.endswith(SOURCE_EXTENSIONS)
286
+ )
283
287
 
284
288
  print(f"Creating submission tarball: {dest}")
285
289
  try:
@@ -464,6 +468,9 @@ class MiscHandler:
464
468
  any_diff = True
465
469
  for line in diff:
466
470
  print(self._colorize_diff_line(line), end='')
471
+ if not line.endswith('\n'):
472
+ no_newline: str = '\'
473
+ print(f'\n{colorize(no_newline, "yellow")}')
467
474
  if skipped:
468
475
  print(colorize("\nSkipped (sources):", 'yellow'))
469
476
  for src in skipped:
@@ -161,6 +161,16 @@ class SBoCreate: # pylint: disable=too-few-public-methods,too-many-instance-att
161
161
  self.misc.reverse_deps(name, tree=parsed.tree)
162
162
  raise SystemExit(0)
163
163
 
164
+ def _cmd_repo(self, parsed: argparse.Namespace) -> None:
165
+ repo_dispatch: dict[str, Callable[[], None]] = {
166
+ 'diff': lambda: self.misc.diff_package(parsed.names, context=parsed.unified),
167
+ 'sync': self.git.sync_repo,
168
+ 'submit': self.git.submit_package,
169
+ 'copy': self.misc.copy_package,
170
+ 'log': self.git.log_branch,
171
+ }
172
+ repo_dispatch[parsed.repo_command]()
173
+
164
174
  def arguments(self, parsed: argparse.Namespace) -> None:
165
175
  """Dispatch the parsed CLI subcommand to the appropriate handler.
166
176
 
@@ -183,14 +193,10 @@ class SBoCreate: # pylint: disable=too-few-public-methods,too-many-instance-att
183
193
  'package': lambda: self._cmd_package(parsed),
184
194
  'edit': lambda: self.misc.edit_files(editor=parsed.editor),
185
195
  'rename': lambda: self.misc.rename_package(parsed.name),
186
- 'copy': self.misc.copy_package,
187
196
  'fetch': lambda: self._cmd_fetch(parsed),
188
197
  'deps': lambda: self._cmd_deps(parsed),
189
198
  'rdeps': lambda: self._cmd_rdeps(parsed),
190
- 'diff': lambda: self.misc.diff_package(parsed.names,
191
- context=parsed.unified),
192
- 'submit': self.git.submit_package,
193
- 'sync': self.git.sync_repo,
199
+ 'repo': lambda: self._cmd_repo(parsed),
194
200
  'update': self.misc.update_templates,
195
201
  'maintainer': lambda: self.misc.configure_maintainer(show=parsed.show),
196
202
  'repology': lambda: self.repology.check_maintainer(
@@ -12,27 +12,34 @@ from datetime import date
12
12
  from pathlib import Path
13
13
 
14
14
  SOURCE_EXTENSIONS: tuple[str, ...] = (
15
- '.tar.gz', '.tar.bz2', '.tar.xz', '.tar.lz', '.tar.zst',
16
- '.tgz', '.tbz2', '.zip', '.7z',
15
+ ".tar.gz",
16
+ ".tar.bz2",
17
+ ".tar.xz",
18
+ ".tar.lz",
19
+ ".tar.zst",
20
+ ".tgz",
21
+ ".tbz2",
22
+ ".zip",
23
+ ".7z",
17
24
  )
18
- UNSUPPORTED: tuple[str, str] = ('UNSUPPORTED', 'UNTESTED')
25
+ UNSUPPORTED: tuple[str, str] = ("UNSUPPORTED", "UNTESTED")
19
26
  EMAIL_RE = re.compile(
20
- r'^[^@\s]+@[^@\s]+\.[^@\s]+' # standard: user@domain.tld
21
- r'|'
22
- r'^.+\s+[Aa][Tt]\s+[^@\s]+\.[^@\s]+' # semi-obfuscated: user AT domain.tld
23
- r'|'
24
- r'^.+\s+[Aa][Tt]\s+.+\s+[Dd][Oo][Tt]\s+.+$' # full obfuscated: user AT domain DOT tld
27
+ r"^[^@\s]+@[^@\s]+\.[^@\s]+" # standard: user@domain.tld
28
+ r"|"
29
+ r"^.+\s+[Aa][Tt]\s+[^@\s]+\.[^@\s]+" # semi-obfuscated: user AT domain.tld
30
+ r"|"
31
+ r"^.+\s+[Aa][Tt]\s+.+\s+[Dd][Oo][Tt]\s+.+$" # full obfuscated: user AT domain DOT tld
25
32
  )
26
33
 
27
34
  ANSI: dict[str, str] = {
28
- 'cyan': '\033[96m',
29
- 'green': '\033[92m',
30
- 'red': '\033[91m',
31
- 'yellow': '\033[93m',
32
- 'reset': '\033[0m',
35
+ "cyan": "\033[96m",
36
+ "green": "\033[92m",
37
+ "red": "\033[91m",
38
+ "yellow": "\033[93m",
39
+ "reset": "\033[0m",
33
40
  }
34
41
 
35
- PROG: str = Path(sys.argv[0]).name if sys.argv else 'sbo-create'
42
+ PROG: str = Path(sys.argv[0]).name if sys.argv else "sbo-create"
36
43
 
37
44
 
38
45
  class _Color: # pylint: disable=too-few-public-methods
@@ -64,7 +71,9 @@ def colorize(text: str, color: str) -> str:
64
71
  return f"{ANSI.get(color, '')}{text}{ANSI['reset']}"
65
72
 
66
73
 
67
- def confirm(message: str, before_newline: bool = True, after_newline: bool = True) -> bool:
74
+ def confirm(
75
+ message: str, before_newline: bool = True, after_newline: bool = True
76
+ ) -> bool:
68
77
  """Prompt for yes/no confirmation.
69
78
 
70
79
  Handles Ctrl-C (KeyboardInterrupt) and Ctrl-D (EOFError) as a decline
@@ -87,10 +96,10 @@ def confirm(message: str, before_newline: bool = True, after_newline: bool = Tru
87
96
  raise SystemExit(0) from exc
88
97
  if after_newline:
89
98
  print()
90
- return answer in ('y', 'yes')
99
+ return answer in ("y", "yes")
91
100
 
92
101
 
93
- def select_option(prompt: str, options: list[str], default: str = '') -> str:
102
+ def select_option(prompt: str, options: list[str], default: str = "") -> str:
94
103
  """Select an option from a list using arrow keys; Enter confirms.
95
104
 
96
105
  Left/Up arrow moves to the previous option; Right/Down moves to the next.
@@ -113,10 +122,10 @@ def select_option(prompt: str, options: list[str], default: str = '') -> str:
113
122
 
114
123
  def render() -> None:
115
124
  parts: list[str] = [
116
- colorize(f'[{opt}]', 'cyan') if i == idx else f' {opt} '
125
+ colorize(f"[{opt}]", "cyan") if i == idx else f" {opt} "
117
126
  for i, opt in enumerate(options)
118
127
  ]
119
- sys.stdout.write(f'\r{prompt}: {" ".join(parts)}\033[K')
128
+ sys.stdout.write(f"\r{prompt}: {' '.join(parts)}\033[K")
120
129
  sys.stdout.flush()
121
130
 
122
131
  fd: int = sys.stdin.fileno()
@@ -126,22 +135,22 @@ def select_option(prompt: str, options: list[str], default: str = '') -> str:
126
135
  render()
127
136
  while True:
128
137
  ch: str = sys.stdin.read(1)
129
- if ch in ('\r', '\n'):
138
+ if ch in ("\r", "\n"):
130
139
  chosen: str = options[idx]
131
- sys.stdout.write(f'\r{prompt}: {colorize(chosen, "cyan")}\033[K\r\n')
140
+ sys.stdout.write(f"\r{prompt}: {colorize(chosen, 'cyan')}\033[K\r\n")
132
141
  sys.stdout.flush()
133
142
  return chosen
134
- if ch == '\x1b':
143
+ if ch == "\x1b":
135
144
  nxt: str = sys.stdin.read(1)
136
- if nxt == '[':
145
+ if nxt == "[":
137
146
  arrow: str = sys.stdin.read(1)
138
- if arrow in ('C', 'B'): # right / down
147
+ if arrow in ("C", "B"): # right / down
139
148
  idx = (idx + 1) % len(options)
140
- elif arrow in ('D', 'A'): # left / up
149
+ elif arrow in ("D", "A"): # left / up
141
150
  idx = (idx - 1) % len(options)
142
- elif ch == '\x03':
151
+ elif ch == "\x03":
143
152
  raise KeyboardInterrupt
144
- elif ch == '\x04':
153
+ elif ch == "\x04":
145
154
  raise EOFError
146
155
  render()
147
156
  finally:
@@ -163,15 +172,16 @@ class InfoData: # pylint: disable=too-many-instance-attributes
163
172
  arm_support: True if the package targets ARM64 instead of x86_64.
164
173
  labels: Ordered tuple of .info field name prefixes used when parsing.
165
174
  """
166
- version: str = ''
167
- homepage: str = ''
168
- download_x86: str = ''
169
- md5sum_x86: str = ''
170
- download_x86_64: str = ''
171
- md5sum_x86_64: str = ''
172
- requires: str = ''
173
- maintainer: str = ''
174
- email: str = ''
175
+
176
+ version: str = ""
177
+ homepage: str = ""
178
+ download_x86: str = ""
179
+ md5sum_x86: str = ""
180
+ download_x86_64: str = ""
181
+ md5sum_x86_64: str = ""
182
+ requires: str = ""
183
+ maintainer: str = ""
184
+ email: str = ""
175
185
  arm_support: bool = False
176
186
  labels: tuple[str, ...] = field(default_factory=tuple)
177
187
 
@@ -193,17 +203,18 @@ class MaintainerData: # pylint: disable=too-many-instance-attributes
193
203
  git_commit_cmd: Git commit command prefix (default: 'git commit -sm').
194
204
  git_push_cmd: Git push command (default: 'git push').
195
205
  """
196
- name: str = ''
197
- email: str = ''
198
- where_you_live: str = ''
199
- editor: str = ''
200
- editor_options: str = ''
201
- sbo_repo_version: str = '15.0'
202
- sbo_repo_path: str = ''
203
- git_branch: str = ''
204
- git_main_branch: str = 'master'
205
- git_commit_cmd: str = 'git commit -sm'
206
- git_push_cmd: str = 'git push'
206
+
207
+ name: str = ""
208
+ email: str = ""
209
+ where_you_live: str = ""
210
+ editor: str = ""
211
+ editor_options: str = ""
212
+ sbo_repo_version: str = "15.0"
213
+ sbo_repo_path: str = ""
214
+ git_branch: str = ""
215
+ git_main_branch: str = "master"
216
+ git_commit_cmd: str = "git commit -sm"
217
+ git_push_cmd: str = "git push"
207
218
 
208
219
 
209
220
  @dataclass
@@ -226,16 +237,27 @@ class AppContext: # pylint: disable=too-many-instance-attributes
226
237
  sbo_versions: Cached mapping of package name to its version string.
227
238
  strip_comments: If True, remove inline comments from generated SlackBuilds.
228
239
  """
240
+
229
241
  info: InfoData = field(default_factory=InfoData)
230
242
  maintainer: MaintainerData = field(default_factory=MaintainerData)
231
243
 
232
- prg_name: str = ''
233
- template: str = ''
244
+ prg_name: str = ""
245
+ template: str = ""
234
246
  current_folder: Path = field(default_factory=Path.cwd)
235
- config_path: Path = field(default_factory=lambda: Path(Path.home(), '.config', 'sbo-create'))
236
- templates_path: Path = field(default_factory=lambda: Path(Path.home(), '.local', 'share', 'sbo-create', 'templates'))
237
- repo_path: Path = field(default_factory=lambda: Path(Path.home(), '.local', 'share', 'sbo-create', 'repo'))
238
- config_file: str = 'config.conf'
247
+ config_path: Path = field(
248
+ default_factory=lambda: Path(Path.home(), ".config", "sbo-create")
249
+ )
250
+ templates_path: Path = field(
251
+ default_factory=lambda: Path(
252
+ Path.home(), ".local", "share", "sbo-create", "templates"
253
+ )
254
+ )
255
+ repo_path: Path = field(
256
+ default_factory=lambda: Path(
257
+ Path.home(), ".local", "share", "sbo-create", "repo"
258
+ )
259
+ )
260
+ config_file: str = "config.conf"
239
261
  year: int = field(default_factory=lambda: date.today().year)
240
262
  sbo_packages: dict[str, str] = field(default_factory=dict)
241
263
  sbo_requires: dict[str, list[str]] = field(default_factory=dict)
@@ -254,9 +276,16 @@ def set_x86_labels(info: InfoData) -> None:
254
276
  """
255
277
  info.arm_support = False
256
278
  info.labels = (
257
- 'PRGNAM=', 'VERSION=', 'HOMEPAGE=', 'DOWNLOAD=',
258
- 'MD5SUM=', 'DOWNLOAD_x86_64=', 'MD5SUM_x86_64=',
259
- 'REQUIRES=', 'MAINTAINER=', 'EMAIL='
279
+ "PRGNAM=",
280
+ "VERSION=",
281
+ "HOMEPAGE=",
282
+ "DOWNLOAD=",
283
+ "MD5SUM=",
284
+ "DOWNLOAD_x86_64=",
285
+ "MD5SUM_x86_64=",
286
+ "REQUIRES=",
287
+ "MAINTAINER=",
288
+ "EMAIL=",
260
289
  )
261
290
 
262
291
 
@@ -271,7 +300,14 @@ def set_arm_labels(info: InfoData) -> None:
271
300
  """
272
301
  info.arm_support = True
273
302
  info.labels = (
274
- 'PRGNAM=', 'VERSION=', 'HOMEPAGE=', 'DOWNLOAD=',
275
- 'MD5SUM=', 'DOWNLOAD_ARM64=', 'MD5SUM_ARM64=',
276
- 'REQUIRES=', 'MAINTAINER=', 'EMAIL='
303
+ "PRGNAM=",
304
+ "VERSION=",
305
+ "HOMEPAGE=",
306
+ "DOWNLOAD=",
307
+ "MD5SUM=",
308
+ "DOWNLOAD_ARM64=",
309
+ "MD5SUM_ARM64=",
310
+ "REQUIRES=",
311
+ "MAINTAINER=",
312
+ "EMAIL=",
277
313
  )
File without changes