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.
- {sbo_create-2.2.0 → sbo_create-2.3.0}/PKG-INFO +6 -5
- {sbo_create-2.2.0 → sbo_create-2.3.0}/README.md +5 -4
- {sbo_create-2.2.0 → sbo_create-2.3.0}/pyproject.toml +1 -1
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/__metadata__.py +1 -1
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/cli.py +52 -24
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/bump.py +14 -7
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/git.py +72 -5
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/misc.py +10 -3
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/main.py +11 -5
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/models.py +95 -59
- {sbo_create-2.2.0 → sbo_create-2.3.0}/LICENSE +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/__init__.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/__init__.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/download.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/fallback.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/fetch.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/files.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/fix.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/repology.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/scan.py +0 -0
- {sbo_create-2.2.0 → sbo_create-2.3.0}/sbo_create/handlers/validate.py +0 -0
- {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.
|
|
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`
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
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`
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
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 @@
|
|
|
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,
|
|
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
|
-
|
|
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
|
-
#
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
sp.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
help='
|
|
277
|
-
sp.set_defaults(command='
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
235
|
-
if
|
|
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
|
|
239
|
-
|
|
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
|
|
258
|
-
into a gzip-compressed tar archive placed in the parent
|
|
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] =
|
|
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
|
-
'
|
|
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
|
-
|
|
16
|
-
|
|
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] = (
|
|
25
|
+
UNSUPPORTED: tuple[str, str] = ("UNSUPPORTED", "UNTESTED")
|
|
19
26
|
EMAIL_RE = re.compile(
|
|
20
|
-
r
|
|
21
|
-
r
|
|
22
|
-
r
|
|
23
|
-
r
|
|
24
|
-
r
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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(
|
|
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 (
|
|
99
|
+
return answer in ("y", "yes")
|
|
91
100
|
|
|
92
101
|
|
|
93
|
-
def select_option(prompt: str, options: list[str], default: 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
|
|
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
|
|
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 (
|
|
138
|
+
if ch in ("\r", "\n"):
|
|
130
139
|
chosen: str = options[idx]
|
|
131
|
-
sys.stdout.write(f
|
|
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 ==
|
|
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 (
|
|
147
|
+
if arrow in ("C", "B"): # right / down
|
|
139
148
|
idx = (idx + 1) % len(options)
|
|
140
|
-
elif arrow in (
|
|
149
|
+
elif arrow in ("D", "A"): # left / up
|
|
141
150
|
idx = (idx - 1) % len(options)
|
|
142
|
-
elif ch ==
|
|
151
|
+
elif ch == "\x03":
|
|
143
152
|
raise KeyboardInterrupt
|
|
144
|
-
elif ch ==
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
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
|