@codebend3r/che 1.0.0 โ†’ 1.2.0

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 (53) hide show
  1. package/README.md +65 -25
  2. package/bin/art.py +123 -0
  3. package/bin/che.py +209 -568
  4. package/bin/commands.py +220 -231
  5. package/bin/doctor.py +220 -0
  6. package/bin/drives/eject-all-drives.py +21 -38
  7. package/bin/drives/mount-all-drives.py +18 -62
  8. package/bin/drives/ping-nas.py +77 -105
  9. package/bin/files/compress-folders.py +7 -11
  10. package/bin/files/delete-by-ext.py +10 -14
  11. package/bin/files/delete-empty-folders.py +33 -37
  12. package/bin/files/delete-smb-files.py +12 -21
  13. package/bin/files/files-under-size.py +5 -11
  14. package/bin/files/find-largest-files.py +21 -25
  15. package/bin/files/list-permission.py +40 -0
  16. package/bin/files/make-alpha-dir.py +2 -0
  17. package/bin/git/all-actions.py +350 -253
  18. package/bin/git/checkout-my-branches.py +42 -63
  19. package/bin/git/clean-stale-branches.py +23 -48
  20. package/bin/git/prune-worktrees.py +12 -51
  21. package/bin/git/sync-all-branches.py +54 -283
  22. package/bin/git/update-local-branches.py +67 -73
  23. package/bin/gitutils.py +263 -0
  24. package/bin/install.py +393 -1188
  25. package/bin/machine.py +345 -0
  26. package/bin/media.py +127 -0
  27. package/bin/menu.py +437 -0
  28. package/bin/nas.py +102 -0
  29. package/bin/rcfile.py +380 -0
  30. package/bin/shellgen.py +508 -428
  31. package/bin/system/btop-launch.py +17 -13
  32. package/bin/system/update-brew.py +57 -68
  33. package/bin/tui.py +156 -153
  34. package/bin/utils.py +165 -237
  35. package/bin/video/delete-duplicate-videos.py +64 -62
  36. package/bin/video/detect-green-magenta-videos.py +105 -95
  37. package/bin/video/find-movie-by-year.py +3 -0
  38. package/bin/video/find-video-mkv-issues.py +48 -65
  39. package/bin/video/fix-codecs.py +25 -30
  40. package/bin/video/largest-tv-shows.py +60 -57
  41. package/bin/video/remove-metadata.py +3 -6
  42. package/bin/video/rename-video-file.py +74 -84
  43. package/bin/video/scan-videos-audio-language.py +16 -33
  44. package/bin/video/show-codecs.py +20 -24
  45. package/bin/video/validate-video-files.py +5 -8
  46. package/bin/video/video-list.py +17 -20
  47. package/package.json +4 -10
  48. package/shell/che.bash +33 -33
  49. package/shell/che.fish +7 -7
  50. package/shell/che.zsh +33 -33
  51. package/shell/completions/_che +19 -19
  52. package/shell/completions/che.bash +15 -15
  53. package/shell/completions/che.fish +34 -50
@@ -20,9 +20,9 @@ from pathlib import Path
20
20
 
21
21
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
22
22
 
23
- from utils import info, require_binary, run, warning
23
+ from utils import atomic_write_text, info, require_binary, run, run_cli, warning
24
24
 
25
- __version__ = "1.0.1"
25
+ __version__ = "1.1.0"
26
26
 
27
27
  CONF = Path.home() / ".config" / "btop" / "btop.conf"
28
28
  LIGHT_THEME = "gruvbox_light"
@@ -37,10 +37,15 @@ Description:
37
37
  Any extra arguments are passed straight through to btop.
38
38
  """
39
39
 
40
- # `[^\S\n]` rather than `\s`: \s matches newlines, so a config with a line
40
+ # `[^\S\r\n]` rather than `\s`: \s matches newlines, so a config with a line
41
41
  # break between "color_theme" and "=" would have two lines collapsed into one.
42
42
  # perl -p works a line at a time and cannot do that.
43
- _COLOR_THEME_RE = re.compile(r"^color_theme[^\S\n]*=.*$", re.MULTILINE)
43
+ #
44
+ # DELIBERATE DIVERGENCE: the value stops at `\r` as well as `\n`. perl's `.`
45
+ # (like Python's) matches `\r`, so the shell version turned the rewritten line
46
+ # of a CRLF config into LF. A CR-only config has no `\n` at all, so to both it
47
+ # was one long line, and the match replaced everything after the theme.
48
+ _COLOR_THEME_RE = re.compile(r"^color_theme[^\S\r\n]*=[^\r\n]*", re.MULTILINE)
44
49
 
45
50
 
46
51
  def current_theme() -> str:
@@ -61,11 +66,12 @@ def apply_theme(theme: str) -> None:
61
66
  return
62
67
 
63
68
  try:
64
- # newline="" so existing CRLF (or CR) line endings survive the
65
- # round-trip; read_text/write_text would rewrite every line ending in
66
- # the file, where `perl -i` only ever touched the matched line.
67
- # surrogateescape so a config carrying non-UTF-8 bytes still round-trips
68
- # instead of raising and leaving btop unlaunchable.
69
+ # newline="" so existing CRLF (or CR) line endings survive the read;
70
+ # read_text would turn every line ending in the file into \n, where
71
+ # `perl -i` only ever touched the matched line. surrogateescape so a
72
+ # config carrying non-UTF-8 bytes still round-trips instead of raising
73
+ # and leaving btop unlaunchable. atomic_write_text writes both back
74
+ # untouched.
69
75
  with CONF.open("r", encoding="utf-8", errors="surrogateescape", newline="") as handle:
70
76
  original = handle.read()
71
77
  except OSError as exc:
@@ -79,8 +85,7 @@ def apply_theme(theme: str) -> None:
79
85
 
80
86
  if updated != original:
81
87
  try:
82
- with CONF.open("w", encoding="utf-8", errors="surrogateescape", newline="") as handle:
83
- handle.write(updated)
88
+ atomic_write_text(CONF, updated)
84
89
  except OSError as exc:
85
90
  warning(f"Could not write {CONF}: {exc}; launching with existing theme")
86
91
  return
@@ -107,8 +112,7 @@ def main(argv: list[str] | None = None) -> int:
107
112
  # identical to running btop directly, exactly like `exec btop "$@"`.
108
113
  # argv[0] stays the bare name, which is what btop echoes in its own usage.
109
114
  os.execv(btop, ["btop", *args]) # noqa: S606 - absolute path, no shell
110
- return 0 # unreachable; execv never returns
111
115
 
112
116
 
113
117
  if __name__ == "__main__":
114
- sys.exit(main())
118
+ run_cli(main)
@@ -19,12 +19,16 @@ from __future__ import annotations
19
19
 
20
20
  import sys
21
21
  from pathlib import Path
22
+ from typing import NamedTuple
22
23
 
23
24
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
24
25
 
25
26
  from utils import (
26
27
  add_bool_flag,
27
28
  build_parser,
29
+ dry_run_complete,
30
+ footer_rule,
31
+ heading,
28
32
  info,
29
33
  log,
30
34
  note,
@@ -35,34 +39,28 @@ from utils import (
35
39
  warning,
36
40
  )
37
41
 
38
- __version__ = "1.0.0"
42
+ __version__ = "1.1.0"
39
43
 
40
44
 
41
- def brew(
42
- argv: list[str],
43
- *,
44
- dry_run: bool,
45
- dry_run_argv: list[str] | None = None,
46
- echo: bool = False,
47
- ) -> None:
48
- """Run a brew subcommand, or its --dry-run form.
45
+ class Step(NamedTuple):
46
+ """One brew command the update runs, and what skips it."""
49
47
 
50
- Homebrew's own ``--dry-run`` exits non-zero in some situations that are not
51
- really failures, so the preview calls are non-fatal - the same ``|| true``
52
- the shell version used. The real calls stay fatal.
48
+ banner: str
49
+ command: list[str]
50
+ skip: str = "" # what a passed --no-* flag skips, for the skip note
51
+
52
+
53
+ def brew(command: list[str], *, dry_run: bool) -> None:
54
+ """Run a brew subcommand, or preview it with Homebrew's own ``--dry-run``.
55
+
56
+ That ``--dry-run`` exits non-zero in some situations that are not really
57
+ failures, so the preview calls are non-fatal - the same ``|| true`` the
58
+ shell version used. The real calls stay fatal.
53
59
  """
54
60
  if dry_run:
55
- if dry_run_argv is None:
56
- warning(f" ๐Ÿช„ Would run: {' '.join(argv)}")
57
- return
58
- run(dry_run_argv, check=False)
59
- return
60
-
61
- # Only `brew update` echoed its command in the shell version; the other
62
- # steps called brew directly, and brew narrates itself.
63
- if echo:
64
- log(f" โ–ถ {' '.join(argv)}")
65
- run(argv)
61
+ run([*command, "--dry-run"], check=False)
62
+ else:
63
+ run(command)
66
64
 
67
65
 
68
66
  def main(argv: list[str] | None = None) -> int:
@@ -71,9 +69,11 @@ def main(argv: list[str] | None = None) -> int:
71
69
  description="๐Ÿบ Update Homebrew, upgrade formulae and casks, then clean up.",
72
70
  )
73
71
  add_bool_flag(parser, "--dry-run", help="Show what would change, change nothing")
74
- add_bool_flag(parser, "--no-cask", dest="no_cask", help="Skip upgrading casks")
75
- add_bool_flag(parser, "--no-cleanup", dest="no_cleanup", help="Skip 'brew cleanup'")
76
- add_bool_flag(parser, "--no-autoremove", dest="no_autoremove", help="Skip 'brew autoremove'")
72
+ # Bare spellings only, as the shell version took them: `--no-cask=false`
73
+ # is a double negative, not a way to ask for casks.
74
+ add_bool_flag(parser, "--no-cask", allow_value=False, help="Skip upgrading casks")
75
+ add_bool_flag(parser, "--no-cleanup", allow_value=False, help="Skip 'brew cleanup'")
76
+ add_bool_flag(parser, "--no-autoremove", allow_value=False, help="Skip 'brew autoremove'")
77
77
  add_bool_flag(parser, "--greedy", help="Pass --greedy to cask upgrade")
78
78
  add_bool_flag(parser, "--doctor", help="Run 'brew doctor' at the very end")
79
79
  args = parser.parse_args(argv)
@@ -81,50 +81,40 @@ def main(argv: list[str] | None = None) -> int:
81
81
  require_binary("brew", hint="Install Homebrew first: https://brew.sh")
82
82
 
83
83
  dry = args.dry_run
84
- header_suffix = " ๐ŸŒต (dry run)" if dry else ""
85
- note("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•")
86
- note(f"๐Ÿบ update-brew{header_suffix}")
87
- note("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•")
84
+ heading("๐Ÿบ update-brew", dry_run=dry)
88
85
 
89
- # 1. Refresh Homebrew itself and tap metadata
86
+ # 1. Refresh Homebrew itself and tap metadata. brew has no preview for
87
+ # this, and it is the one step whose command the shell version echoed;
88
+ # brew narrates the others itself.
90
89
  info("๐Ÿ“ก Updating Homebrew metadata...")
91
- brew(["brew", "update"], dry_run=dry, echo=True)
92
-
93
- # 2. Upgrade formulae
94
- info("โฌ†๏ธ Upgrading formulae...")
95
- brew(["brew", "upgrade"], dry_run=dry, dry_run_argv=["brew", "upgrade", "--dry-run"])
96
-
97
- # 3. Upgrade casks (apps)
98
- if args.no_cask:
99
- note("โญ๏ธ Skipping cask upgrades (--no-cask)")
100
- else:
101
- info("๐Ÿ“ฆ Upgrading casks...")
102
- cask_argv = ["brew", "upgrade", "--cask"]
103
- if args.greedy:
104
- cask_argv.append("--greedy")
105
- brew(cask_argv, dry_run=dry, dry_run_argv=[*cask_argv, "--dry-run"])
106
-
107
- # 4. Remove unused dependencies
108
- if args.no_autoremove:
109
- note("โญ๏ธ Skipping autoremove (--no-autoremove)")
90
+ if dry:
91
+ warning(" ๐Ÿช„ Would run: brew update")
110
92
  else:
111
- info("๐Ÿงน Removing unused dependencies...")
112
- brew(
93
+ log(" โ–ถ brew update")
94
+ run(["brew", "update"])
95
+
96
+ # 2-5.
97
+ cask = ["brew", "upgrade", "--cask", *(["--greedy"] if args.greedy else [])]
98
+ steps = (
99
+ Step("โฌ†๏ธ Upgrading formulae...", ["brew", "upgrade"]),
100
+ Step("๐Ÿ“ฆ Upgrading casks...", cask, "cask upgrades (--no-cask)" if args.no_cask else ""),
101
+ Step(
102
+ "๐Ÿงน Removing unused dependencies...",
113
103
  ["brew", "autoremove"],
114
- dry_run=dry,
115
- dry_run_argv=["brew", "autoremove", "--dry-run"],
116
- )
117
-
118
- # 5. Cleanup old versions + cache
119
- if args.no_cleanup:
120
- note("โญ๏ธ Skipping cleanup (--no-cleanup)")
121
- else:
122
- info("๐Ÿ—‘๏ธ Cleaning up old versions and cache...")
123
- brew(
104
+ "autoremove (--no-autoremove)" if args.no_autoremove else "",
105
+ ),
106
+ Step(
107
+ "๐Ÿ—‘๏ธ Cleaning up old versions and cache...",
124
108
  ["brew", "cleanup", "-s"],
125
- dry_run=dry,
126
- dry_run_argv=["brew", "cleanup", "--dry-run", "-s"],
127
- )
109
+ "cleanup (--no-cleanup)" if args.no_cleanup else "",
110
+ ),
111
+ )
112
+ for step in steps:
113
+ if step.skip:
114
+ note(f"โญ๏ธ Skipping {step.skip}")
115
+ continue
116
+ info(step.banner)
117
+ brew(step.command, dry_run=dry)
128
118
 
129
119
  # 6. Optional doctor pass - advisory, never fatal
130
120
  if args.doctor:
@@ -133,10 +123,9 @@ def main(argv: list[str] | None = None) -> int:
133
123
  if completed.returncode != 0:
134
124
  warning("โš ๏ธ brew doctor reported issues (non-fatal)")
135
125
 
136
- print()
137
- note("โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€")
126
+ footer_rule()
138
127
  if dry:
139
- log("๐Ÿ’ก Dry run complete. Nothing was actually changed.")
128
+ dry_run_complete()
140
129
  else:
141
130
  success("๐ŸŽ‰ Homebrew is up to date and tidy! ๐Ÿบโœจ")
142
131
  return 0