Skip to content

Commit

Permalink
Allow users to answer e at prompts (#183)
Browse files Browse the repository at this point in the history
* Allow users to answer e at prompts

* Rename to currently_explaining

* Speeling

* Show options fully in prompt

* Better coloring
  • Loading branch information
Hoverbear authored Jan 12, 2023
1 parent 4338b78 commit f1df7ed
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 57 deletions.
2 changes: 1 addition & 1 deletion src/action/base/move_unpacked_nix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)],
)]
Expand Down
4 changes: 2 additions & 2 deletions src/action/common/configure_nix_daemon_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
],
)]
Expand Down
22 changes: 9 additions & 13 deletions src/action/common/create_nix_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,17 @@ impl Action for CreateNixTree {
}

fn execute_description(&self) -> Vec<ActionDescription> {
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::<Vec<_>>()
.join(", ")
),
],
create_directory_descriptions,
)]
}

Expand Down
48 changes: 41 additions & 7 deletions src/cli/interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,57 @@ 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<str>, default: bool) -> eyre::Result<bool> {
pub(crate) async fn prompt(
question: impl AsRef<str>,
default: PromptChoice,
currently_explaining: bool,
) -> eyre::Result<PromptChoice> {
let stdout = stdout();
let mut term =
term::terminfo::TerminfoTerminal::new(stdout).ok_or(eyre!("Couldn't get terminal"))?;
let with_confirm = format!(
"\
{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())?;
Expand All @@ -29,10 +62,11 @@ pub(crate) async fn confirm(question: impl AsRef<str>, 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)
Expand Down
62 changes: 42 additions & 20 deletions src/cli/subcommand/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
},
}
}
}

Expand All @@ -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;
Expand Down
25 changes: 17 additions & 8 deletions src/cli/subcommand/uninstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
},
}
}
}

Expand Down
7 changes: 1 addition & 6 deletions src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?
Expand Down

0 comments on commit f1df7ed

Please sign in to comment.