sbo-create 2.3.0__tar.gz → 2.3.2__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.3.0 → sbo_create-2.3.2}/PKG-INFO +1 -1
  2. {sbo_create-2.3.0 → sbo_create-2.3.2}/pyproject.toml +5 -1
  3. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/__metadata__.py +1 -2
  4. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/bump.py +1 -2
  5. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/download.py +2 -2
  6. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/files.py +9 -2
  7. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/fix.py +19 -11
  8. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/git.py +9 -2
  9. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/misc.py +31 -8
  10. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/validate.py +44 -8
  11. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/models.py +1 -0
  12. {sbo_create-2.3.0 → sbo_create-2.3.2}/LICENSE +0 -0
  13. {sbo_create-2.3.0 → sbo_create-2.3.2}/README.md +0 -0
  14. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/__init__.py +0 -0
  15. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/cli.py +0 -0
  16. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/__init__.py +0 -0
  17. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/fallback.py +0 -0
  18. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/fetch.py +0 -0
  19. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/repology.py +0 -0
  20. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/handlers/scan.py +0 -0
  21. {sbo_create-2.3.0 → sbo_create-2.3.2}/sbo_create/main.py +0 -0
  22. {sbo_create-2.3.0 → sbo_create-2.3.2}/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.0
3
+ Version: 2.3.2
4
4
  Summary: SBo tool for creating SlackBuilds.
5
5
  Keywords: sbo,templates,slackbuild
6
6
  Author-email: Dimitris Zlatanidis <dslackw@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "sbo-create"
7
- version = "2.3.0"
7
+ version = "2.3.2"
8
8
  authors = [
9
9
  { name = "Dimitris Zlatanidis", email = "dslackw@gmail.com" },
10
10
  ]
@@ -35,6 +35,10 @@ dependencies = [
35
35
  "urllib3>=1.26"
36
36
  ]
37
37
 
38
+ [tool.isort]
39
+ profile = "black"
40
+ known_first_party = ["sbo_create"]
41
+
38
42
  [tool.mypy]
39
43
  strict = true
40
44
  warn_return_any = true
@@ -4,8 +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, 3, 0)
8
- __version__: str = f'{__version_info__[0]}.{__version_info__[1]}.{__version_info__[2]}'
7
+ __version__: str = '2.3.2'
9
8
  __license__: str = 'GNU General Public License v3 (GPLv3)'
10
9
  __email__: str = 'dslackw@gmail.com'
11
10
  __website__: str = 'https://gitlab.com/dslackw/sbo-create'
@@ -81,8 +81,7 @@ class BumpHandler: # pylint: disable=too-few-public-methods
81
81
  """
82
82
  if len(values) == 1:
83
83
  return f'{label}="{values[0]}"'
84
- indent: str = ' ' * (len(label) + 2)
85
- body: str = f' \\\n{indent}'.join(values)
84
+ body: str = ' \\\n '.join(values)
86
85
  return f'{label}="{body}"'
87
86
 
88
87
  def _bump_md5sums(self, content: str) -> tuple[str, bool]:
@@ -167,7 +167,7 @@ class DownloadHandler:
167
167
  def download(self, link: str) -> bool:
168
168
  """Download a single source file using wget into the current folder.
169
169
 
170
- Runs wget with --continue so partial downloads are resumed. stdout and
170
+ Runs wget with -N so partial downloads are replace. stdout and
171
171
  stderr are suppressed to keep terminal output clean.
172
172
 
173
173
  Args:
@@ -177,6 +177,6 @@ class DownloadHandler:
177
177
  True if wget exited with return code 0, False otherwise.
178
178
  """
179
179
  rc: int = subprocess.run(
180
- ['wget', '--continue', link], check=False,
180
+ ['wget', '-N', link], check=False,
181
181
  stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL).returncode
182
182
  return rc == 0
@@ -9,8 +9,15 @@ import sys
9
9
  from collections.abc import Callable
10
10
  from pathlib import Path
11
11
 
12
- from sbo_create.models import (PROG, AppContext, colorize, confirm,
13
- select_option, set_arm_labels, set_x86_labels)
12
+ from sbo_create.models import (
13
+ PROG,
14
+ AppContext,
15
+ colorize,
16
+ confirm,
17
+ select_option,
18
+ set_arm_labels,
19
+ set_x86_labels,
20
+ )
14
21
  from sbo_create.template_loader import TemplateLoader
15
22
 
16
23
 
@@ -8,8 +8,13 @@ import re
8
8
  from pathlib import Path
9
9
 
10
10
  from sbo_create.handlers.files import FileHandler
11
- from sbo_create.models import (SOURCE_EXTENSIONS, UNSUPPORTED, AppContext,
12
- colorize, confirm)
11
+ from sbo_create.models import (
12
+ SOURCE_EXTENSIONS,
13
+ UNSUPPORTED,
14
+ AppContext,
15
+ colorize,
16
+ confirm,
17
+ )
13
18
 
14
19
 
15
20
  class FixHandler: # pylint: disable=too-few-public-methods
@@ -27,11 +32,13 @@ class FixHandler: # pylint: disable=too-few-public-methods
27
32
  """Auto-fix trailing whitespace in a file, optionally fixing the final newline.
28
33
 
29
34
  Strips trailing whitespace from each line while preserving backslash
30
- continuations. Optionally ensures the file ends with a blank line (for README).
35
+ continuations. Optionally ensures the file ends with exactly one newline,
36
+ stripping any extra trailing blank lines (for README).
31
37
 
32
38
  Args:
33
39
  filepath: Path to the file to fix.
34
- fix_newline: If True, ensure the file ends with '\\n\\n' (blank line).
40
+ fix_newline: If True, ensure the file ends with exactly '\\n' (strips
41
+ extra trailing blank lines).
35
42
 
36
43
  Returns:
37
44
  A tuple of (ws_fixed, newline_fixed) — True for each fix that was applied.
@@ -57,9 +64,10 @@ class FixHandler: # pylint: disable=too-few-public-methods
57
64
 
58
65
  newline_fixed: bool = False
59
66
  if fix_newline:
60
- newline_fixed = not content.endswith('\n\n')
67
+ correct: str = content.rstrip('\n') + '\n'
68
+ newline_fixed = content != correct
61
69
  if newline_fixed:
62
- content = content.rstrip('\n') + '\n\n'
70
+ content = correct
63
71
 
64
72
  if ws_fixed or newline_fixed:
65
73
  filepath.write_text(content, encoding='utf-8')
@@ -418,10 +426,10 @@ class FixHandler: # pylint: disable=too-few-public-methods
418
426
  )
419
427
  if not tarballs:
420
428
  return False
421
- print("Source tarballs to remove:\n")
429
+ print(f"Source tarballs to remove ({len(tarballs)}):\n")
422
430
  for f in tarballs:
423
431
  print(f" {f.name}")
424
- if not confirm("Remove?", before_newline=True, after_newline=False):
432
+ if not confirm(f"Remove {colorize(str(len(tarballs)), 'red')} file(s)?", before_newline=True, after_newline=True):
425
433
  return False
426
434
  for f in tarballs:
427
435
  f.unlink()
@@ -443,10 +451,10 @@ class FixHandler: # pylint: disable=too-few-public-methods
443
451
  name: str = self.ctx.prg_name
444
452
  folder: Path = self.ctx.current_folder
445
453
  candidates: list[tuple[str, bool]] = [
446
- (f'{name}.info', False),
454
+ (f'{name}.info', True),
447
455
  ('README', True),
448
- (f'{name}.SlackBuild', False),
449
- ('slack-desc', False),
456
+ (f'{name}.SlackBuild', True),
457
+ ('slack-desc', True),
450
458
  ]
451
459
  width: int = max(
452
460
  (len(filename)
@@ -234,8 +234,15 @@ class GitHandler:
234
234
 
235
235
  entries: list[tuple[str, str]] = []
236
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, ''))
237
+ m = re.match(r'^(\S+/\S+):\s+.*?[Vv]ersion\s+(\S+?)\.?\s*$', subject)
238
+ if m:
239
+ entries.append((m.group(1), m.group(2)))
240
+ else:
241
+ pkg_m = re.match(r'^(\S+/\S+):\s*(.*)', subject)
242
+ if pkg_m:
243
+ entries.append((pkg_m.group(1), pkg_m.group(2).rstrip('.')))
244
+ else:
245
+ entries.append((subject, ''))
239
246
 
240
247
  print(f"Commits on {colorize(branch, 'cyan')} (since {main}):\n")
241
248
  for pkg, ver in entries:
@@ -17,8 +17,14 @@ from urllib3.exceptions import HTTPError # pylint: disable=import-error
17
17
  from sbo_create.handlers.fallback import SboResolver
18
18
  from sbo_create.handlers.files import FileHandler
19
19
  from sbo_create.handlers.validate import ValidateHandler
20
- from sbo_create.models import (EMAIL_RE, PROG, SOURCE_EXTENSIONS, AppContext,
21
- colorize, confirm)
20
+ from sbo_create.models import (
21
+ EMAIL_RE,
22
+ PROG,
23
+ SOURCE_EXTENSIONS,
24
+ AppContext,
25
+ colorize,
26
+ confirm,
27
+ )
22
28
 
23
29
 
24
30
  def _print_tree_branch( # pylint: disable=too-many-arguments,too-many-positional-arguments
@@ -433,7 +439,7 @@ class MiscHandler:
433
439
  raise SystemExit(1) from exc
434
440
  raise SystemExit(0)
435
441
 
436
- def _diff_files(self, sbo_pkg_path: Path, local_path: Path, context: int) -> bool: # pylint: disable=too-many-locals
442
+ def _diff_files(self, sbo_pkg_path: Path, local_path: Path, context: int) -> bool: # pylint: disable=[too-many-locals, too-many-branches]
437
443
  """Compute and print a unified diff between SBo repo files and local files.
438
444
 
439
445
  Args:
@@ -444,9 +450,21 @@ class MiscHandler:
444
450
  Returns:
445
451
  True if at least one file had differences, False otherwise.
446
452
  """
453
+ try:
454
+ repo_files: set[str] = {f.name for f in sbo_pkg_path.iterdir() if f.is_file()}
455
+ local_files: set[str] = {f.name for f in local_path.iterdir() if f.is_file()}
456
+ except FileNotFoundError as err:
457
+ if not sbo_pkg_path.is_dir():
458
+ print(colorize(f"Error: SBo repository path not found: '{sbo_pkg_path}'", "red"))
459
+ elif not local_path.is_dir():
460
+ print(colorize(f"Error: Local package path not found: '{local_path}'", "red"))
461
+ else:
462
+ print(colorize(f"Error: Path not found: '{err.filename}'", "red"))
463
+ return False
464
+
447
465
  any_diff: bool = False
448
466
  skipped: list[str] = []
449
- for filename in sorted(f.name for f in sbo_pkg_path.iterdir() if f.is_file()):
467
+ for filename in sorted(repo_files):
450
468
  if filename.endswith(SOURCE_EXTENSIONS):
451
469
  if (local_path / filename).is_file():
452
470
  skipped.append(filename)
@@ -476,8 +494,9 @@ class MiscHandler:
476
494
  for src in skipped:
477
495
  print(src)
478
496
 
479
- repo_files: set[str] = {f.name for f in sbo_pkg_path.iterdir() if f.is_file()}
480
- local_files: set[str] = {f.name for f in local_path.iterdir() if f.is_file()}
497
+ # repo_files: set[str] = {f.name for f in sbo_pkg_path.iterdir() if f.is_file()}
498
+ # local_files: set[str] = {f.name for f in local_path.iterdir() if f.is_file()}
499
+
481
500
  new_files: list[str] = sorted(local_files - repo_files)
482
501
  if new_files:
483
502
  print("\nNew in local (not in SBo repo):")
@@ -516,8 +535,12 @@ class MiscHandler:
516
535
  if not sbo_pkg_path.is_dir():
517
536
  print(colorize(f"{PROG}: '{category}/{name}' not found in repo.", 'red'))
518
537
  continue
519
- local_path: Path = (self.ctx.current_folder if from_cwd
520
- else self.ctx.current_folder / orig_name)
538
+
539
+ if from_cwd or self.ctx.current_folder.name == name:
540
+ local_path: Path = self.ctx.current_folder
541
+ else:
542
+ local_path = self.ctx.current_folder / orig_name
543
+
521
544
  if sbo_pkg_path.resolve() == local_path.resolve():
522
545
  print(colorize(
523
546
  f"{PROG}: '{name}' local path and repo path are the same directory.\n"
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/python3
2
2
  # -*- coding: utf-8 -*-
3
+ # pylint: disable=too-many-lines
3
4
 
4
5
  from __future__ import annotations
5
6
 
@@ -12,8 +13,14 @@ import urllib3 # pylint: disable=import-error
12
13
  from sbo_create.handlers.fallback import SboResolver
13
14
  from sbo_create.handlers.files import FileHandler
14
15
  from sbo_create.handlers.fix import FixHandler
15
- from sbo_create.models import (EMAIL_RE, PROG, SOURCE_EXTENSIONS, UNSUPPORTED,
16
- AppContext, colorize)
16
+ from sbo_create.models import (
17
+ EMAIL_RE,
18
+ PROG,
19
+ SOURCE_EXTENSIONS,
20
+ UNSUPPORTED,
21
+ AppContext,
22
+ colorize,
23
+ )
17
24
 
18
25
 
19
26
  class ValidateHandler:
@@ -251,20 +258,46 @@ class ValidateHandler:
251
258
  errors.append(f"{colorize(filename, 'red')}: required file missing")
252
259
  return errors
253
260
 
254
- def _check_readme(self) -> list[str]:
255
- """Check that README exists, is not empty and ends with a blank line.
261
+ def _check_readme(self) -> tuple[list[str], list[str]]:
262
+ """Check that README exists and is not empty.
256
263
 
257
264
  Returns:
258
- List of error message strings (empty if all checks pass).
265
+ A tuple of (errors, warnings).
259
266
  """
260
267
  errors: list[str] = []
268
+ warnings: list[str] = []
261
269
  readme: Path = Path(self.ctx.current_folder, 'README')
262
270
  if readme.is_file():
263
271
  content: str = readme.read_text(encoding='utf-8')
264
272
  if not content.strip():
265
273
  errors.append(f"{colorize('README', 'red')}: is empty")
266
- elif not content.endswith('\n\n'):
267
- errors.append(f"{colorize('README', 'red')}: must end with an empty line")
274
+ return errors, warnings
275
+
276
+ def _check_trailing_newlines(self) -> list[str]:
277
+ """Check that no submission file ends with trailing blank lines or missing newline.
278
+
279
+ Checks .info, .SlackBuild, slack-desc and README.
280
+
281
+ Returns:
282
+ List of error message strings (empty if all checks pass).
283
+ """
284
+ errors: list[str] = []
285
+ name: str = self.ctx.prg_name
286
+ for filename in [f'{name}.info', f'{name}.SlackBuild', 'slack-desc', 'README']:
287
+ path: Path = Path(self.ctx.current_folder, filename)
288
+ if not path.is_file():
289
+ continue
290
+ content: str = path.read_text(encoding='utf-8')
291
+ if not content.strip():
292
+ continue
293
+ if not content.endswith('\n'):
294
+ errors.append(
295
+ f"{colorize(filename, 'red')}: must end with a newline"
296
+ " (run --fix to add)")
297
+ elif content.endswith('\n\n'):
298
+ errors.append(
299
+ f"{colorize(filename, 'red')}: ends with trailing blank line(s)"
300
+ " (run --fix to remove)")
268
301
  return errors
269
302
 
270
303
  def _check_no_sources(self) -> list[str]:
@@ -754,14 +787,16 @@ class ValidateHandler:
754
787
  info_lines: list[str] = info_file.read_text(encoding='utf-8').splitlines()
755
788
  info_var_errors = self._validate_info_variables(info_lines)
756
789
 
790
+ readme_errors, readme_warnings = self._check_readme()
757
791
  md5_errors, warnings = self._check_md5sums(show_warnings)
792
+ warnings = readme_warnings + warnings
758
793
  copyright_warn: str = self._check_copyright_year()
759
794
  if copyright_warn:
760
795
  warnings.append(copyright_warn)
761
796
 
762
797
  groups: list[tuple[str, list[str]]] = [
763
798
  ('Required files', required_errors),
764
- ('README', self._check_readme()),
799
+ ('README', readme_errors),
765
800
  ('.info variables', info_var_errors),
766
801
  ('.info consistency', self._check_info_consistency()),
767
802
  ('.info quotes', self._check_info_quotes()),
@@ -769,6 +804,7 @@ class ValidateHandler:
769
804
  ('slack-desc', self._check_slack_desc()),
770
805
  ('REQUIRES', self._check_requires_existence()),
771
806
  ('Trailing whitespace', self._check_trailing_whitespace()),
807
+ ('Trailing newlines', self._check_trailing_newlines()),
772
808
  ('ASCII encoding', self._check_ascii()),
773
809
  ('MD5SUM', md5_errors),
774
810
  ]
@@ -21,6 +21,7 @@ SOURCE_EXTENSIONS: tuple[str, ...] = (
21
21
  ".tbz2",
22
22
  ".zip",
23
23
  ".7z",
24
+ ".crate",
24
25
  )
25
26
  UNSUPPORTED: tuple[str, str] = ("UNSUPPORTED", "UNTESTED")
26
27
  EMAIL_RE = re.compile(
File without changes
File without changes
File without changes