From f1df7ed432b9a20fbfdb0228470ed01832f48a26 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Thu, 12 Jan 2023 11:29:13 -0800 Subject: [PATCH] Allow users to answer e at prompts (#183) * Allow users to answer e at prompts * Rename to currently_explaining * Speeling * Show options fully in prompt * Better coloring --- src/action/base/move_unpacked_nix.rs | 2 +- .../common/configure_nix_daemon_service.rs | 4 +- src/action/common/create_nix_tree.rs | 22 +++---- src/cli/interaction.rs | 48 +++++++++++--- src/cli/subcommand/install.rs | 62 +++++++++++++------ src/cli/subcommand/uninstall.rs | 25 +++++--- src/plan.rs | 7 +-- 7 files changed, 113 insertions(+), 57 deletions(-) diff --git a/src/action/base/move_unpacked_nix.rs b/src/action/base/move_unpacked_nix.rs index 4e53c01..f5aaae9 100644 --- a/src/action/base/move_unpacked_nix.rs +++ b/src/action/base/move_unpacked_nix.rs @@ -42,7 +42,7 @@ impl Action for MoveUnpackedNix { vec![ActionDescription::new( format!("Move the downloaded Nix into `/nix`"), vec![format!( - "Nix is being downloaded to `{}` and should be in `nix`", + "Nix is being downloaded to `{}` and should be in `/nix`", self.src.display(), )], )] diff --git a/src/action/common/configure_nix_daemon_service.rs b/src/action/common/configure_nix_daemon_service.rs index 57ab7bf..8fc418a 100644 --- a/src/action/common/configure_nix_daemon_service.rs +++ b/src/action/common/configure_nix_daemon_service.rs @@ -61,8 +61,8 @@ impl Action for ConfigureNixDaemonService { self.tracing_synopsis(), vec![ "Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(), - "Run `systemctl link {SERVICE_SRC}`".to_string(), - "Run `systemctl link {SOCKET_SRC}`".to_string(), + format!("Run `systemctl link {SERVICE_SRC}`"), + format!("Run `systemctl link {SOCKET_SRC}`"), "Run `systemctl daemon-reload`".to_string(), ], )] diff --git a/src/action/common/create_nix_tree.rs b/src/action/common/create_nix_tree.rs index 5e99fad..3664a08 100644 --- a/src/action/common/create_nix_tree.rs +++ b/src/action/common/create_nix_tree.rs @@ -52,21 +52,17 @@ impl Action for CreateNixTree { } fn execute_description(&self) -> Vec { + let Self { create_directories } = &self; + + let mut create_directory_descriptions = Vec::new(); + for create_directory in create_directories { + if let Some(val) = create_directory.describe_execute().iter().next() { + create_directory_descriptions.push(val.description.clone()) + } + } vec![ActionDescription::new( self.tracing_synopsis(), - vec![ - format!( - "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" - ), - format!( - "Creates: {}", - PATHS - .iter() - .map(|v| format!("`{v}`")) - .collect::>() - .join(", ") - ), - ], + create_directory_descriptions, )] } diff --git a/src/cli/interaction.rs b/src/cli/interaction.rs index badea08..9c56e66 100644 --- a/src/cli/interaction.rs +++ b/src/cli/interaction.rs @@ -3,11 +3,22 @@ use std::io::{stdin, stdout, BufRead, Write}; use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PromptChoice { + Yes, + No, + Explain, +} + // Do not try to get clever! // // Mac is extremely janky if you `curl $URL | sudo sh` and the TTY may not be set up right. // The below method was adopted from Rustup at https://github.com/rust-lang/rustup/blob/3331f34c01474bf216c99a1b1706725708833de1/src/cli/term2.rs#L37 -pub(crate) async fn confirm(question: impl AsRef, default: bool) -> eyre::Result { +pub(crate) async fn prompt( + question: impl AsRef, + default: PromptChoice, + currently_explaining: bool, +) -> eyre::Result { let stdout = stdout(); let mut term = term::terminfo::TerminfoTerminal::new(stdout).ok_or(eyre!("Couldn't get terminal"))?; @@ -15,12 +26,34 @@ pub(crate) async fn confirm(question: impl AsRef, default: bool) -> eyre::R "\ {question}\n\ \n\ - {are_you_sure} ({yes}/{no}): \ + {are_you_sure} ({yes}/{no}{maybe_explain}): \ ", question = question.as_ref(), are_you_sure = "Proceed?".bold(), - no = if default { "n" } else { "N" }.red(), - yes = if default { "Y" } else { "y" }.green(), + no = if default == PromptChoice::No { + "[N]o" + } else { + "[n]o" + } + .red(), + yes = if default == PromptChoice::Yes { + "[Y]es" + } else { + "[y]es" + } + .green(), + maybe_explain = if !currently_explaining { + format!( + "/{}", + if default == PromptChoice::Explain { + "[E]xplain" + } else { + "[e]xplain" + } + ) + } else { + "".into() + }, ); term.write_all(with_confirm.as_bytes())?; @@ -29,10 +62,11 @@ pub(crate) async fn confirm(question: impl AsRef, default: bool) -> eyre::R let input = read_line()?; let r = match &*input.to_lowercase() { - "y" | "yes" => true, - "n" | "no" => false, + "y" | "yes" => PromptChoice::Yes, + "n" | "no" => PromptChoice::No, + "e" | "explain" => PromptChoice::Explain, "" => default, - _ => false, + _ => PromptChoice::No, }; Ok(r) diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index a472f5f..e9d7b0f 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -6,7 +6,11 @@ use std::{ use crate::{ action::ActionState, - cli::{ensure_root, interaction, signal_channel, CommandExecute}, + cli::{ + ensure_root, + interaction::{self, PromptChoice}, + signal_channel, CommandExecute, + }, error::HasExpectedErrors, plan::RECEIPT_LOCATION, planner::Planner, @@ -144,15 +148,23 @@ impl CommandExecute for Install { }; if !no_confirm { - if !interaction::confirm( - install_plan - .describe_install(explain) - .map_err(|e| eyre!(e))?, - true, - ) - .await? - { - interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; + let mut currently_explaining = explain; + loop { + match interaction::prompt( + install_plan + .describe_install(currently_explaining) + .map_err(|e| eyre!(e))?, + PromptChoice::Yes, + currently_explaining, + ) + .await? + { + PromptChoice::Yes => break, + PromptChoice::Explain => currently_explaining = true, + PromptChoice::No => { + interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await + }, + } } } @@ -175,16 +187,26 @@ impl CommandExecute for Install { }; eprintln!("{}", "Installation failure, offering to revert...".red()); - if !interaction::confirm( - install_plan - .describe_uninstall(explain) - .map_err(|e| eyre!(e))?, - true, - ) - .await? - { - interaction::clean_exit_with_message("Okay, didn't do anything! Bye!") - .await; + let mut currently_explaining = explain; + loop { + match interaction::prompt( + install_plan + .describe_uninstall(currently_explaining) + .map_err(|e| eyre!(e))?, + PromptChoice::Yes, + currently_explaining, + ) + .await? + { + PromptChoice::Yes => break, + PromptChoice::Explain => currently_explaining = true, + PromptChoice::No => { + interaction::clean_exit_with_message( + "Okay, didn't do anything! Bye!", + ) + .await + }, + } } let rx2 = tx.subscribe(); let res = install_plan.uninstall(rx2).await; diff --git a/src/cli/subcommand/uninstall.rs b/src/cli/subcommand/uninstall.rs index 1680d7d..61d4771 100644 --- a/src/cli/subcommand/uninstall.rs +++ b/src/cli/subcommand/uninstall.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - cli::{ensure_root, signal_channel}, + cli::{ensure_root, interaction::PromptChoice, signal_channel}, error::HasExpectedErrors, plan::RECEIPT_LOCATION, InstallPlan, @@ -127,13 +127,22 @@ impl CommandExecute for Uninstall { let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?; if !no_confirm { - if !interaction::confirm( - plan.describe_uninstall(explain).map_err(|e| eyre!(e))?, - true, - ) - .await? - { - interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; + let mut currently_explaining = explain; + loop { + match interaction::prompt( + plan.describe_uninstall(currently_explaining) + .map_err(|e| eyre!(e))?, + PromptChoice::Yes, + currently_explaining, + ) + .await? + { + PromptChoice::Yes => break, + PromptChoice::Explain => currently_explaining = true, + PromptChoice::No => { + interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await + }, + } } } diff --git a/src/plan.rs b/src/plan.rs index 58184a4..4cab190 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -66,15 +66,10 @@ impl InstallPlan { \n\ {plan_settings}\n\ \n\ - The following actions will be taken{maybe_explain}:\n\ + The following actions will be taken:\n\ \n\ {actions}\n\ ", - maybe_explain = if !explain { - " (`--explain` for more context)" - } else { - "" - }, planner = planner.typetag_name(), plan_settings = planner .settings()?