diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index f2e937d8..8b0fff79 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -90,8 +90,10 @@ When you are ready to cut a new release, follow these steps to generate the
tbump X.Y.Z --no-push
```
- _Note: `tbump` will automatically trigger the `before_commit` hook, run
- `update-package-metadata.py`, generate the changelog and bundle everything
+ _Note: `tbump` will automatically trigger the `before_commit` hooks to
+ regenerate the status screenshot
+ (`scripts/maintainer/generate-screenshot.py`), run
+ `update-package-metadata.py`, generate the changelog, and bundle everything
into a single release commit and local tag._
💡 **Tip for Release Candidates:** You can safely use standard SemVer tags
diff --git a/README.md b/README.md
index 639dafdf..e3a75987 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,12 @@ updates. It features Snapper-integrated atomic rollbacks, multi-channel alerting
user-level execution. Designed for reliability and ease of use, it ensures your
Flatpak environment remains current and resilient.
+
+
+

+
+
## ✨ Features
- **Automated Flatpak Updates:** Keep your flatpak applications up-to-date
@@ -154,8 +160,8 @@ first run).
To trigger an update manually or use the advanced CLI:
```text
-usage: flatpak-automatic [-h] [-d] [-t] [-f] [-s] [-l] [-a] [-c] [-r]
- [--desktop-mode] [-e] [-x]
+usage: flatpak-automatic [-h] [-d] [-t] [-f] [-s] [-B] [-v] [-V] [-l] [-a] [-c]
+ [-r] [--desktop-mode] [-e] [-x]
Flatpak Automatic - Advanced Update Automation
@@ -165,6 +171,9 @@ options:
-t, --test-notify Send a test notification to configured endpoints and exit.
-f, --force Force the update process, ignoring safeguards.
-s, --status Display system monitoring overview and exit.
+ -B, --hide-banner Hide the header ASCII banner upon execution.
+ -v, --version Show application version and exit.
+ -V, --verbose Enable verbose output (e.g. detailed status view with -s/--status).
-l, --history Display recent update history from journalctl and exit.
-a, --apply-schedule Apply systemd timer overrides based on config settings.
-c, --check-config Validate and print the current configuration, then exit.
diff --git a/assets/status-screenshot.png b/assets/status-screenshot.png
new file mode 100644
index 00000000..26a68567
Binary files /dev/null and b/assets/status-screenshot.png differ
diff --git a/config/config.default.yaml b/config/config.default.yaml
index 095a5e84..0710701c 100644
--- a/config/config.default.yaml
+++ b/config/config.default.yaml
@@ -38,6 +38,15 @@ exclusions: []
# Type: string, Default: always
auto_notify: always
+# ------------------------------------------------------------------------------
+# CLI & Interface Settings
+# ------------------------------------------------------------------------------
+
+cli:
+ # Hide the ASCII header banner when executing from terminal.
+ # Type: boolean, Default: false
+ hide_banner: false
+
# ------------------------------------------------------------------------------
# Systemd Timer Schedule
# ------------------------------------------------------------------------------
diff --git a/config/config.example.yaml b/config/config.example.yaml
index 10ee4483..49dd2f48 100644
--- a/config/config.example.yaml
+++ b/config/config.example.yaml
@@ -31,6 +31,14 @@ exclusions:
# Notification trigger policy: 'always', 'on-change', 'on-failure', or 'never'.
auto_notify: always
+# ------------------------------------------------------------------------------
+# CLI & Interface Settings
+# ------------------------------------------------------------------------------
+
+cli:
+ # Hide the ASCII header banner when executing from terminal.
+ hide_banner: false
+
# ------------------------------------------------------------------------------
# Systemd Timer Schedule
# ------------------------------------------------------------------------------
diff --git a/config/config.user.default.yaml b/config/config.user.default.yaml
index 2af14165..889829e0 100644
--- a/config/config.user.default.yaml
+++ b/config/config.user.default.yaml
@@ -32,6 +32,15 @@ exclusions: []
# Type: string, Default: always
auto_notify: always
+# ------------------------------------------------------------------------------
+# CLI & Interface Settings
+# ------------------------------------------------------------------------------
+
+cli:
+ # Hide the ASCII header banner when executing from terminal.
+ # Type: boolean, Default: false
+ hide_banner: false
+
# ------------------------------------------------------------------------------
# Systemd Timer Schedule (User Scope)
# ------------------------------------------------------------------------------
diff --git a/config/templates/default_mail_success.md b/config/templates/default_mail_success.md
index 7e9841e4..c2e0ff5e 100644
--- a/config/templates/default_mail_success.md
+++ b/config/templates/default_mail_success.md
@@ -1,6 +1,7 @@
# ✅ Update Report: Success
-The `flatpak-automatic` agent has successfully installed **${UPDATE_COUNT}**
+The `flatpak-automatic` agent has successfully installed
+**${UPDATE_COUNT}**
updates on the host **${HOSTNAME}**.
## Installation Details
diff --git a/docs/development.md b/docs/development.md
index 163291df..003466ea 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -75,6 +75,20 @@ Usage:
[--dry-run]
```
+## Screenshot Generation Tool
+
+A `scripts/maintainer/generate-screenshot.py` script automatically captures the
+output of `flatpak-automatic -s` (including the full ASCII header banner) and
+renders a high-resolution terminal screenshot image saved at
+`assets/status-screenshot.png` for display in `README.md`. This script is
+automatically invoked during release version bumps via `tbump`.
+
+Usage:
+
+```bash
+python3 scripts/maintainer/generate-screenshot.py
+```
+
## Testing Matrix
This project enforces a Shift-Left testing approach.
diff --git a/docs/flatpak-automatic.1 b/docs/flatpak-automatic.1
index d7e69698..a119e7e4 100644
--- a/docs/flatpak-automatic.1
+++ b/docs/flatpak-automatic.1
@@ -4,7 +4,7 @@
flatpak-automatic \- Secure, systemd-native automation for Flatpak updates
.SH SYNOPSIS
.B flatpak-automatic
-[\fB\-d\fR] [\fB\-t\fR] [\fB\-f\fR] [\fB\-s\fR] [\fB\-l\fR] [\fB\-a\fR] [\fB\-c\fR] [\fB\-r\fR] [\fB\-e\fR] [\fB\-x\fR] [\fB\-\-desktop\-mode\fR]
+[\fB\-d\fR] [\fB\-t\fR] [\fB\-f\fR] [\fB\-s\fR] [\fB\-B\fR] [\fB\-v\fR] [\fB\-V\fR] [\fB\-l\fR] [\fB\-a\fR] [\fB\-c\fR] [\fB\-r\fR] [\fB\-e\fR] [\fB\-x\fR] [\fB\-\-desktop\-mode\fR]
.SH DESCRIPTION
.B flatpak-automatic
is a secure, systemd-native automation wrapper for Flatpak updates. It features Snapper-integrated atomic rollbacks, multi-channel alerting (Apprise, Mail, Webhooks, Desktop), and supports both system-wide and rootless user-level execution. Designed for reliability and ease of use, it ensures your Flatpak environment remains current and resilient.
@@ -32,6 +32,15 @@ Force the update process, bypassing the minimum delay guard and auto_update poli
.BR \-s ", " \-\-status
Display a system monitoring overview of configuration and pending updates.
.TP
+.BR \-B ", " \-\-hide\-banner
+Hide the header ASCII banner upon execution.
+.TP
+.BR \-v ", " \-\-version
+Display the installed application version and exit.
+.TP
+.BR \-V ", " \-\-verbose
+Enable verbose output (e.g. detailed status view of installed Flatpaks and runtimes with -s/--status).
+.TP
.BR \-l ", " \-\-history
Display recent execution history from journalctl.
.TP
diff --git a/scripts/maintainer/generate-screenshot.py b/scripts/maintainer/generate-screenshot.py
new file mode 100755
index 00000000..94035ba3
--- /dev/null
+++ b/scripts/maintainer/generate-screenshot.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+"""Script to generate terminal status screenshot for README using rich, chrome/cairosvg."""
+
+import os
+from pathlib import Path
+import re
+import shutil
+import subprocess
+import sys
+from PIL import Image, ImageChops
+from rich.console import Console
+from rich.terminal_theme import TerminalTheme
+from rich.text import Text
+
+
+# Authentic terminal color palette matching standard terminal emulators (with rich purple HEADER)
+VIBRANT_TERMINAL_THEME = TerminalTheme(
+ background=(41, 41, 41),
+ foreground=(248, 248, 242),
+ normal=[
+ (0, 0, 0),
+ (255, 85, 85),
+ (80, 250, 123),
+ (241, 250, 140),
+ (139, 233, 253),
+ (189, 147, 249), # Purple (#BD93F9)
+ (0, 229, 255),
+ (191, 191, 191),
+ ],
+ bright=[
+ (98, 114, 164),
+ (255, 110, 110),
+ (105, 255, 148),
+ (255, 255, 165),
+ (120, 190, 255),
+ (189, 147, 249), # Bright Purple (#BD93F9)
+ (0, 229, 255),
+ (255, 255, 255),
+ ],
+)
+
+
+def crop_margins(image_path: Path) -> None:
+ """Crop white outer margins from generated screenshot."""
+ im = Image.open(image_path)
+ im_rgb = im.convert("RGB")
+ bg = Image.new("RGB", im_rgb.size, (255, 255, 255))
+ diff = ImageChops.difference(im_rgb, bg)
+ bbox = diff.getbbox()
+ if bbox:
+ cropped = im.crop(bbox)
+ cropped.save(image_path)
+
+
+def convert_svg_to_png(svg_path: Path, output_png: Path) -> None:
+ """Convert SVG to PNG using Google Chrome/Chromium if available, falling back to cairosvg."""
+ chrome_bin = shutil.which("google-chrome") or shutil.which("chromium")
+
+ if chrome_bin:
+ temp_png = svg_path.parent / "temp_chrome.png"
+ cmd = [
+ chrome_bin,
+ "--headless",
+ "--disable-gpu",
+ "--hide-scrollbars",
+ f"--screenshot={temp_png}",
+ "--window-size=1000,800",
+ str(svg_path),
+ ]
+ subprocess.run(
+ cmd,
+ check=True,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ )
+
+ if temp_png.exists():
+ crop_margins(temp_png)
+ shutil.move(temp_png, output_png)
+ return
+
+ # Fallback to cairosvg
+ import cairosvg
+
+ svg_data = svg_path.read_text(encoding="utf-8")
+ svg_data = re.sub(
+ r"font-family:\s*([^;]+);",
+ r"font-family: \1, 'Noto Color Emoji', 'Noto Emoji', 'Segoe UI Emoji', 'Apple Color Emoji', 'Symbola', monospace;",
+ svg_data,
+ )
+ cairosvg.svg2png(
+ bytestring=svg_data.encode("utf-8"),
+ write_to=str(output_png),
+ )
+
+
+def generate_screenshot() -> None:
+ """Run status command as root (or fallback), capture terminal output with ASCII header banner, and export to PNG image."""
+ script_path = Path(__file__).resolve()
+ repo_root = script_path.parent.parent.parent
+ assets_dir = repo_root / "assets"
+ assets_dir.mkdir(parents=True, exist_ok=True)
+ output_png = assets_dir / "status-screenshot.png"
+ temp_svg = assets_dir / "temp_status.svg"
+
+ src_dir = str(repo_root / "src")
+ if src_dir not in sys.path:
+ sys.path.insert(0, src_dir)
+
+ from flatpak_automatic.cli import banner
+
+ cli_script = repo_root / "src" / "flatpak-automatic.py"
+
+ output_text = None
+
+ # Try non-interactive sudo first, then interactive sudo, then non-root execution
+ attempts = [
+ ["sudo", "-n", sys.executable, str(cli_script), "-s"],
+ ["sudo", sys.executable, str(cli_script), "-s"],
+ [sys.executable, str(cli_script), "-s"],
+ ]
+
+ if os.geteuid() == 0:
+ attempts.insert(0, [sys.executable, str(cli_script), "-s"])
+
+ for cmd in attempts:
+ try:
+ result = subprocess.run(
+ cmd,
+ cwd=repo_root,
+ capture_output=True,
+ text=True,
+ check=True,
+ timeout=5,
+ )
+ output_text = result.stdout.strip()
+ if output_text:
+ break
+ except Exception:
+ continue
+
+ if not output_text:
+ # Ultimate fallback
+ result = subprocess.run(
+ [sys.executable, str(cli_script), "-s"],
+ cwd=repo_root,
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ output_text = result.stdout.strip()
+
+ banner_text = banner()
+ full_output = f"{banner_text}\n{output_text}"
+
+ console = Console(record=True, width=100)
+ rich_text = Text.from_ansi(full_output)
+ console.print(rich_text)
+
+ svg_data = console.export_svg(
+ title="flatpak-automatic -s", theme=VIBRANT_TERMINAL_THEME
+ )
+ temp_svg.write_text(svg_data, encoding="utf-8")
+
+ convert_svg_to_png(temp_svg, output_png)
+
+ if temp_svg.exists():
+ temp_svg.unlink()
+
+ print(f"Successfully generated screenshot at: {output_png}")
+
+
+if __name__ == "__main__":
+ generate_screenshot()
diff --git a/src/flatpak_automatic/__main__.py b/src/flatpak_automatic/__main__.py
index b0dc36f8..f9aa188b 100644
--- a/src/flatpak_automatic/__main__.py
+++ b/src/flatpak_automatic/__main__.py
@@ -4,6 +4,7 @@
import signal
import subprocess
from typing import Any
+from . import __version__
from .cli import get_parser, banner
from .config import ConfigManager, StateManager
from .core import AutomationEngine
@@ -15,18 +16,26 @@ def main() -> None:
# 1. Initialize Parser & Logging
parser = get_parser()
args = parser.parse_args()
+
+ if args.version:
+ print(f"flatpak-automatic {__version__}")
+ sys.exit(0)
+
setup_logging()
# Dynamic Flatpak Scope for non-root execution
user_scope = os.geteuid() != 0
flatpak_scope = ["--user"] if user_scope else ["--system"]
- if sys.stdout.isatty():
- print(banner())
-
# 2. Load Configuration & State
config = ConfigManager.load()
+ # Determine if banner should be displayed
+ hide_banner = args.hide_banner or config.get("cli", {}).get("hide_banner", False)
+
+ if sys.stdout.isatty() and not hide_banner:
+ print(banner())
+
def sighup_handler(signum: int, frame: Any) -> None:
logging.info("SIGHUP received. Hot-reloading configuration...")
nonlocal config
@@ -160,8 +169,8 @@ def sighup_handler(signum: int, frame: Any) -> None:
print()
sys.exit(0)
- if args.status:
- engine.print_status_overview()
+ if args.status or args.verbose:
+ engine.print_status_overview(verbose=args.verbose)
sys.exit(0)
if args.history:
diff --git a/src/flatpak_automatic/cli.py b/src/flatpak_automatic/cli.py
index 2fa93b58..b71af3ff 100644
--- a/src/flatpak_automatic/cli.py
+++ b/src/flatpak_automatic/cli.py
@@ -51,6 +51,24 @@ def get_parser() -> BrandedArgumentParser:
action="store_true",
help="Display system monitoring overview and exit.",
)
+ parser.add_argument(
+ "-B",
+ "--hide-banner",
+ action="store_true",
+ help="Hide the header ASCII banner upon execution.",
+ )
+ parser.add_argument(
+ "-v",
+ "--version",
+ action="store_true",
+ help="Show application version and exit.",
+ )
+ parser.add_argument(
+ "-V",
+ "--verbose",
+ action="store_true",
+ help="Enable verbose output (e.g. detailed status view with -s/--status).",
+ )
parser.add_argument(
"-l",
"--history",
diff --git a/src/flatpak_automatic/core.py b/src/flatpak_automatic/core.py
index 5185e7e7..8d516a44 100644
--- a/src/flatpak_automatic/core.py
+++ b/src/flatpak_automatic/core.py
@@ -91,7 +91,7 @@ def dispatch_test_notifications(self) -> None:
update_count=0,
)
- def print_status_overview(self) -> None:
+ def print_status_overview(self, verbose: bool = False) -> None:
from .notifiers import APPRISE_AVAILABLE, MailNotifier
print(
@@ -209,23 +209,74 @@ def print_status_overview(self) -> None:
webhook_icon = "🟢" if webhook_avail else "⚪"
print(f" Network/Webhook: {webhook_icon} {webhook_status}")
- print(f"\n{Colors.OKCYAN}📦 Installed Flatpaks:{Colors.ENDC}")
- result = subprocess.run(
- ["flatpak", "list", "--app", "--columns=application,version"],
- capture_output=True,
- text=True,
- )
exclusions = self.config.get("exclusions", [])
- for line in result.stdout.strip().split("\n"):
- if not line:
- continue
-
- # Simple parsing of application ID (first column)
- app_id = line.split()[0]
- if app_id in exclusions:
- print(f" {Colors.WARNING}{line} (Excluded){Colors.ENDC}")
- else:
- print(f" {line}")
+
+ if verbose:
+ print(
+ f"\n{Colors.OKCYAN}📦 Installed Flatpaks & Runtimes (Verbose):{Colors.ENDC}"
+ )
+ result = subprocess.run(
+ [
+ "flatpak",
+ "list",
+ "--columns=name,application,version,branch,installation",
+ ],
+ capture_output=True,
+ text=True,
+ )
+ raw_lines = [
+ line.split("\t")
+ for line in result.stdout.strip().split("\n")
+ if line.strip()
+ ]
+
+ if raw_lines:
+ headers = [
+ "NAME",
+ "APPLICATION ID",
+ "VERSION",
+ "BRANCH",
+ "INSTALLATION",
+ ]
+ col_widths = [len(h) for h in headers]
+ for row in raw_lines:
+ for i in range(min(5, len(row))):
+ col_widths[i] = max(col_widths[i], len(row[i]))
+
+ header_str = " " + " ".join(
+ f"{headers[i]:<{col_widths[i]}}" for i in range(5)
+ )
+ print(f"{Colors.BOLD}{Colors.OKCYAN}{header_str}{Colors.ENDC}")
+
+ for row in raw_lines:
+ padded_row = row + [""] * (5 - len(row))
+ app_id = padded_row[1]
+ formatted_row = " " + " ".join(
+ f"{padded_row[i]:<{col_widths[i]}}" for i in range(5)
+ )
+
+ if app_id in exclusions:
+ print(
+ f" {Colors.WARNING}{formatted_row.strip()} (Excluded){Colors.ENDC}"
+ )
+ else:
+ print(f"{formatted_row}")
+ else:
+ print(f"\n{Colors.OKCYAN}📦 Installed Flatpaks:{Colors.ENDC}")
+ result = subprocess.run(
+ ["flatpak", "list", "--app", "--columns=application,version"],
+ capture_output=True,
+ text=True,
+ )
+ for line in result.stdout.strip().split("\n"):
+ if not line:
+ continue
+
+ app_id = line.split()[0]
+ if app_id in exclusions:
+ print(f" {Colors.WARNING}{line} (Excluded){Colors.ENDC}")
+ else:
+ print(f" {line}")
print() # Trailing empty line
def run(
diff --git a/tbump.toml b/tbump.toml
index 939887f0..53797280 100644
--- a/tbump.toml
+++ b/tbump.toml
@@ -24,6 +24,10 @@ tag_template = "v{new_version}"
name = "Generate Changelogs and Package Metadata"
cmd = "python3 scripts/build/update-package-metadata.py --version {new_version} --generate-changelog"
+[[before_commit]]
+name = "Generate Terminal Status Screenshot"
+cmd = "python3 scripts/maintainer/generate-screenshot.py"
+
# Pre-run hooks to ensure everything is formatted before tbump tries to commit.
# This prevents failures where hooks modify files during the git commit step.
[[before_commit]]
diff --git a/tests/test_core_status.py b/tests/test_core_status.py
index 5c53d0a6..0df13e0f 100644
--- a/tests/test_core_status.py
+++ b/tests/test_core_status.py
@@ -64,3 +64,38 @@ def test_print_status_timer_disabled(self, mock_run: MagicMock) -> None:
call.args[0] if call.args else "" for call in mock_print.call_args_list
]
assert any("Disabled / Inactive" in arg for arg in printed_args)
+
+ @patch("subprocess.run")
+ @patch("flatpak_automatic.notifiers.MailNotifier.is_available")
+ @patch("flatpak_automatic.notifiers.DesktopNotifier.is_available")
+ @patch("flatpak_automatic.notifiers.WebhookNotifier.is_available")
+ def test_print_status_overview_verbose(
+ self,
+ mock_wh: MagicMock,
+ mock_dt: MagicMock,
+ mock_mail: MagicMock,
+ mock_run: MagicMock,
+ ) -> None:
+ mock_run.side_effect = [
+ MagicMock(returncode=1), # is-enabled
+ MagicMock(returncode=1), # is-active
+ MagicMock(
+ stdout="App\torg.test.App\t1.0\tstable\tsystem\n", returncode=0
+ ), # flatpak list verbose
+ ]
+ mock_mail.return_value = True
+ mock_dt.return_value = True
+ mock_wh.return_value = True
+
+ engine = AutomationEngine({"exclusions": ["org.test.App"]}, {})
+ with patch("builtins.print") as mock_print:
+ engine.print_status_overview(verbose=True)
+ printed_args = [
+ call.args[0] if call.args else "" for call in mock_print.call_args_list
+ ]
+ assert any(
+ "📦 Installed Flatpaks & Runtimes (Verbose):" in arg
+ for arg in printed_args
+ )
+ assert any("APPLICATION ID" in arg for arg in printed_args)
+ assert any("(Excluded)" in arg for arg in printed_args)
diff --git a/tests/test_main_cli.py b/tests/test_main_cli.py
index ff66bf7c..5b99c813 100644
--- a/tests/test_main_cli.py
+++ b/tests/test_main_cli.py
@@ -15,7 +15,7 @@ def test_main_check_config(
mock_config: MagicMock,
mock_parser: MagicMock,
) -> None:
- args = MagicMock()
+ args = MagicMock(version=False)
args.check_config = True
args.reload = False
args.apply_schedule = False
@@ -49,11 +49,13 @@ def test_main_status(
) -> None:
args = MagicMock(
check_config=False,
+ version=False,
reload=False,
apply_schedule=False,
enable_timer=False,
disable_timer=False,
status=True,
+ verbose=False,
history=False,
test_notify=False,
)
@@ -79,11 +81,13 @@ def test_main_enable_timer(
) -> None:
args = MagicMock(
check_config=False,
+ version=False,
reload=False,
apply_schedule=False,
enable_timer=True,
disable_timer=False,
status=False,
+ verbose=False,
history=False,
test_notify=False,
)
@@ -110,11 +114,13 @@ def test_main_disable_timer(
) -> None:
args = MagicMock(
check_config=False,
+ version=False,
reload=False,
apply_schedule=False,
enable_timer=False,
disable_timer=True,
status=False,
+ verbose=False,
history=False,
test_notify=False,
)
@@ -141,11 +147,13 @@ def test_main_reload(
) -> None:
args = MagicMock(
check_config=False,
+ version=False,
reload=True,
apply_schedule=False,
enable_timer=False,
disable_timer=False,
status=False,
+ verbose=False,
history=False,
test_notify=False,
)
@@ -173,11 +181,13 @@ def test_main_history(
) -> None:
args = MagicMock(
check_config=False,
+ version=False,
reload=False,
apply_schedule=False,
enable_timer=False,
disable_timer=False,
status=False,
+ verbose=False,
history=True,
test_notify=False,
)
@@ -202,11 +212,13 @@ def test_main_test_notify(
) -> None:
args = MagicMock(
check_config=False,
+ version=False,
reload=False,
apply_schedule=False,
enable_timer=False,
disable_timer=False,
status=False,
+ verbose=False,
history=False,
test_notify=True,
)
@@ -217,6 +229,92 @@ def test_main_test_notify(
assert e.value.code == 0
mock_engine.return_value.dispatch_test_notifications.assert_called_once()
+ @patch("flatpak_automatic.__main__.get_parser")
+ @patch("flatpak_automatic.__main__.ConfigManager.load")
+ @patch("flatpak_automatic.__main__.StateManager.load")
+ @patch("flatpak_automatic.__main__.AutomationEngine")
+ @patch("sys.stdout.isatty", return_value=True)
+ @patch("flatpak_automatic.__main__.banner")
+ def test_main_hide_banner_cli(
+ self,
+ mock_banner: MagicMock,
+ mock_isatty: MagicMock,
+ mock_engine: MagicMock,
+ mock_state: MagicMock,
+ mock_config: MagicMock,
+ mock_parser: MagicMock,
+ ) -> None:
+ args = MagicMock(
+ check_config=True,
+ hide_banner=True,
+ reload=False,
+ apply_schedule=False,
+ enable_timer=False,
+ disable_timer=False,
+ status=False,
+ verbose=False,
+ history=False,
+ test_notify=False,
+ )
+ mock_parser.return_value.parse_args.return_value = args
+ mock_config.return_value = {"cli": {"hide_banner": False}}
+
+ with pytest.raises(SystemExit) as e:
+ main()
+ assert e.value.code == 0
+ mock_banner.assert_not_called()
+
+ @patch("flatpak_automatic.__main__.get_parser")
+ @patch("flatpak_automatic.__main__.ConfigManager.load")
+ @patch("flatpak_automatic.__main__.StateManager.load")
+ @patch("flatpak_automatic.__main__.AutomationEngine")
+ @patch("sys.stdout.isatty", return_value=True)
+ @patch("flatpak_automatic.__main__.banner")
+ def test_main_hide_banner_config(
+ self,
+ mock_banner: MagicMock,
+ mock_isatty: MagicMock,
+ mock_engine: MagicMock,
+ mock_state: MagicMock,
+ mock_config: MagicMock,
+ mock_parser: MagicMock,
+ ) -> None:
+ args = MagicMock(
+ check_config=True,
+ hide_banner=False,
+ reload=False,
+ apply_schedule=False,
+ enable_timer=False,
+ disable_timer=False,
+ status=False,
+ verbose=False,
+ history=False,
+ test_notify=False,
+ )
+ mock_parser.return_value.parse_args.return_value = args
+ mock_config.return_value = {"cli": {"hide_banner": True}}
+
+ with pytest.raises(SystemExit) as e:
+ main()
+ assert e.value.code == 0
+ mock_banner.assert_not_called()
+
+ @patch("flatpak_automatic.__main__.get_parser")
+ @patch("builtins.print")
+ def test_main_version(
+ self,
+ mock_print: MagicMock,
+ mock_parser: MagicMock,
+ ) -> None:
+ args = MagicMock(version=True)
+ mock_parser.return_value.parse_args.return_value = args
+
+ with pytest.raises(SystemExit) as e:
+ main()
+ assert e.value.code == 0
+ mock_print.assert_called_once()
+ assert "flatpak-automatic" in mock_print.call_args[0][0]
+
@patch("flatpak_automatic.__main__.get_parser")
@patch("flatpak_automatic.__main__.ConfigManager.load")
@patch("flatpak_automatic.__main__.StateManager.load")
@@ -234,11 +332,14 @@ def test_main_apply_schedule(
) -> None:
args = MagicMock(
check_config=False,
+ version=False,
+ hide_banner=False,
reload=False,
apply_schedule=True,
enable_timer=False,
disable_timer=False,
status=False,
+ verbose=False,
history=False,
test_notify=False,
)