From 781de7ae6cca3a48141b6efd53fef91e470665ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 27 Aug 2026 21:16:55 +0200 Subject: [PATCH] v0.3.0: recommended vs custom mode, DE/WM explanations, better UX --- euri_mklive/tui.py | 239 ++++++++++++++++++++++++++++++++++----------- pyproject.toml | 2 +- 2 files changed, 185 insertions(+), 56 deletions(-) diff --git a/euri_mklive/tui.py b/euri_mklive/tui.py index ddd61b8..53211d4 100644 --- a/euri_mklive/tui.py +++ b/euri_mklive/tui.py @@ -195,6 +195,145 @@ def banner() -> None: console.print() +def select_setup_mode() -> str: + """Pick recommended or custom mode.""" + console.print(Panel( + "[bold]How would you like to configure your ISO?[/]\n\n" + " [cyan]Recommended[/] — pick a desktop, we handle the rest\n" + " (void + dinit + musl, base packages included)\n\n" + " [white]Custom[/] — choose every detail yourself\n" + " (any distro, init, libc, packages, etc.)", + border_style="cyan", + padding=(0, 1), + )) + console.print() + choice = questionary.select( + "Setup mode:", + choices=[ + "Recommended — easy setup, just pick your desktop", + "Custom — full control over everything", + ], + ).ask() + if choice is None: + sys.exit(0) + console.print() + return "recommended" if "Recommended" in choice else "custom" + + +def run_recommended() -> dict: + """Simplified flow — pick DE/WM, we handle the rest.""" + banner() + mode = select_mode() + + # auto-pick distro/init/libc/arch + distro = "void" + init = "dinit" + libc = "musl" + arch = "x86_64" + console.print("[bold]Recommended setup[/]\n") + console.print(f" Distro: [cyan]void[/] (independent, fast, musl)") + console.print(f" Init: [cyan]dinit[/] (modern service manager)") + console.print(f" Libc: [cyan]musl[/] (small, secure)") + console.print(f" Arch: [cyan]x86_64[/]") + console.print() + + # DE/WM selection with explanations + de_name, de_pkgs = select_de_wm(distro, explain=True) + + # auto-pick package groups + auto_groups = ["Base", "Networking", "Audio", "Firmware"] + pkgs = [] + for g in auto_groups: + pkgs.extend(COMMON_PACKAGES[g]) + + console.print(f" [dim]Auto-selected groups:[/] {', '.join(auto_groups)}") + + # custom packages + console.print() + custom = questionary.text( + "Additional packages (comma-separated, or empty):", + default="", + ).ask() + if custom and custom.strip(): + for p in custom.split(","): + p = p.strip() + if p: + pkgs.append(p) + + # merge + seen = set() + merged = [] + for p in pkgs + de_pkgs: + if p not in seen: + seen.add(p) + merged.append(p) + + cfg = { + "distro": distro, + "arch": arch, + "init": init, + "libc": libc, + "hostname": "euri", + "packages": merged, + "de_wm": de_name, + "services": [], + "users": [], + "iso": { + "name": "Euri Linux", + "version": "1.0", + "label": "EURI", + }, + "_mode": mode, + } + + if not confirm_build(cfg): + sys.exit(0) + return cfg + + +def run_custom() -> dict: + """Full control flow — choose everything.""" + banner() + mode = select_mode() + distro = select_distro() + init = select_init(distro) + libc = select_libc(distro) + arch = select_arch() + pkgs = select_packages() + de_name, de_pkgs = select_de_wm(distro, explain=False) + iso = configure_iso() + + # merge packages: base groups + DE/WM + seen = set() + merged = [] + for p in pkgs + de_pkgs: + if p not in seen: + seen.add(p) + merged.append(p) + + cfg = { + "distro": distro, + "arch": arch, + "init": init, + "libc": libc, + "hostname": iso["hostname"], + "packages": merged, + "de_wm": de_name, + "services": [], + "users": [], + "iso": { + "name": iso["name"], + "version": iso["version"], + "label": iso["label"], + }, + "_mode": mode, + } + + if not confirm_build(cfg): + sys.exit(0) + return cfg + + def select_mode() -> str: """Select root or fakeroot mode.""" root = _check_root() @@ -265,10 +404,10 @@ def select_init(distro: str) -> str: """Pick init system.""" inits = DISTRO_INFO[distro]["inits"] if len(inits) == 1: - console.print(f" Init system: [cyan]{inits[0]}[/] (only option)\n") return inits[0] - console.print("[bold]Step 2: Init system[/]\n") + console.print("[bold]Init system[/]\n") + console.print(" [dim]Init = how services start. systemd is most common.[/]\n") choice = questionary.select( "Pick init system:", choices=inits, @@ -283,10 +422,10 @@ def select_libc(distro: str) -> str: """Pick libc.""" libcs = DISTRO_INFO[distro]["libc"] if len(libcs) == 1: - console.print(f" Libc: [cyan]{libcs[0]}[/] (only option)\n") return libcs[0] - console.print("[bold]Step 3: Libc[/]\n") + console.print("[bold]Libc[/]\n") + console.print(" [dim]Libc = C library. glibc = wide compatibility, musl = smaller/stricter.[/]\n") choice = questionary.select( "Pick libc:", choices=libcs, @@ -299,9 +438,8 @@ def select_libc(distro: str) -> str: def select_arch() -> str: """Pick architecture.""" - console.print("[bold]Step 4: Architecture[/]\n") choice = questionary.select( - "Pick architecture:", + "Architecture:", choices=["x86_64", "aarch64"], default="x86_64", ).ask() @@ -313,7 +451,7 @@ def select_arch() -> str: def select_packages() -> list[str]: """Pick packages interactively.""" - console.print("[bold]Step 5: Packages[/]\n") + console.print("[bold]Packages[/]\n") selected: list[str] = [] @@ -334,6 +472,9 @@ def select_packages() -> list[str]: sys.exit(0) if pick: groups.append(gname) + console.print(f" [green]+ {gname}[/] ({len(COMMON_PACKAGES[gname])} packages)") + + console.print() for group_name in groups: selected.extend(COMMON_PACKAGES[group_name]) @@ -347,7 +488,6 @@ def select_packages() -> list[str]: deduped.append(p) # custom packages - console.print() custom = questionary.text( "Additional packages (comma-separated, or empty):", default="", @@ -359,13 +499,14 @@ def select_packages() -> list[str]: seen.add(p) deduped.append(p) - console.print(f" [green]{len(deduped)} packages selected[/]\n") + console.print(f" [green]{len(deduped)} packages total[/]\n") return deduped def configure_iso() -> dict: """Configure ISO metadata.""" - console.print("[bold]Step 6: ISO metadata[/]\n") + console.print("[bold]ISO metadata[/]\n") + console.print(" [dim]These identify your ISO image. Defaults are fine for testing.[/]\n") name = questionary.text("ISO name:", default="Euri Linux").ask() if name is None: @@ -420,9 +561,13 @@ def confirm_build(cfg: dict) -> bool: return questionary.confirm("Start build?", default=True).ask() or sys.exit(0) -def select_de_wm(distro: str) -> tuple[str, list[str]]: - """Pick a DE or window manager — returns (name, distro-specific packages).""" - console.print("[bold]Step 5b: Desktop Environment / Window Manager[/]\n") +def select_de_wm(distro: str, explain: bool = False) -> tuple[str, list[str]]: + """Pick a DE or window manager — returns (name, distro-specific packages). + + If explain=True, show longer descriptions for new users. + If explain=False, show compact table for power users. + """ + console.print("[bold]Desktop Environment / Window Manager[/]\n") # map distro to family distro_family = { @@ -432,17 +577,36 @@ def select_de_wm(distro: str) -> tuple[str, list[str]]: "fedora": "fedora", "rocky": "fedora", "almalinux": "fedora", }.get(distro, "arch") + # explanation panel for new users + if explain: + console.print(Panel( + "[bold]What is a DE/WM?[/]\n\n" + " A [bold]Desktop Environment (DE)[/] is a full graphical workspace —\n" + " taskbar, file manager, settings, apps. (GNOME, KDE, XFCE)\n\n" + " A [bold]Window Manager (WM)[/] is just window management —\n" + " lighter, keyboard-driven, you build your own workflow.\n" + " (Sway, i3, Hyprland, bspwm, openbox)\n\n" + " [dim]Both work fine. DE = ready to use. WM = more control.[/]", + border_style="dim", + padding=(0, 1), + )) + console.print() + # show table de_table = Table(show_header=True, border_style="dim", pad_edge=False) de_table.add_column("#", style="cyan", width=3) de_table.add_column("DE/WM", style="bold") de_table.add_column("Type") - de_table.add_column("Description") + de_table.add_column("Description" if explain else "Info") for i, (name, info) in enumerate(DE_WM_PACKAGES.items(), 1): pkgs = info.get(distro_family, []) status = f"[green]{len(pkgs)} pkgs[/]" if pkgs else "[red]unavailable[/]" server = info.get("server", "") - de_table.add_row(str(i), name, server, f"{info['desc']} ({status})") + if explain: + de_table.add_row(str(i), name, server, f"{info['desc']} ({status})") + else: + short = info["desc"].split("—")[0].strip() if "—" in info["desc"] else info["desc"][:30] + de_table.add_row(str(i), name, server, f"{short} ({status})") console.print(de_table) console.print() @@ -484,44 +648,9 @@ def build_progress(cfg: dict, func, *args, **kwargs): def run_interactive() -> dict: - """Full interactive TUI flow. Returns a config dict.""" + """Interactive TUI flow — routes to recommended or custom.""" banner() - mode = select_mode() - distro = select_distro() - init = select_init(distro) - libc = select_libc(distro) - arch = select_arch() - pkgs = select_packages() - de_name, de_pkgs = select_de_wm(distro) - iso = configure_iso() - - # merge packages: base groups + DE/WM - seen = set() - merged = [] - for p in pkgs + de_pkgs: - if p not in seen: - seen.add(p) - merged.append(p) - - cfg = { - "distro": distro, - "arch": arch, - "init": init, - "libc": libc, - "hostname": iso["hostname"], - "packages": merged, - "de_wm": de_name, - "services": [], - "users": [], - "iso": { - "name": iso["name"], - "version": iso["version"], - "label": iso["label"], - }, - "_mode": mode, - } - - if not confirm_build(cfg): - sys.exit(0) - - return cfg + mode = select_setup_mode() + if mode == "recommended": + return run_recommended() + return run_custom() diff --git a/pyproject.toml b/pyproject.toml index 96e32e8..f192a2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "euri-mklive" -version = "0.2.0" +version = "0.3.0" description = "Universal CLI ISO builder — any distro, from any distro" requires-python = ">=3.10" dependencies = ["pyyaml>=6", "click>=8", "rich>=13", "questionary>=2"]