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
This commit is contained in:
parent
4338b78135
commit
f1df7ed432
|
@ -42,7 +42,7 @@ impl Action for MoveUnpackedNix {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Move the downloaded Nix into `/nix`"),
|
format!("Move the downloaded Nix into `/nix`"),
|
||||||
vec![format!(
|
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(),
|
self.src.display(),
|
||||||
)],
|
)],
|
||||||
)]
|
)]
|
||||||
|
|
|
@ -61,8 +61,8 @@ impl Action for ConfigureNixDaemonService {
|
||||||
self.tracing_synopsis(),
|
self.tracing_synopsis(),
|
||||||
vec![
|
vec![
|
||||||
"Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(),
|
"Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(),
|
||||||
"Run `systemctl link {SERVICE_SRC}`".to_string(),
|
format!("Run `systemctl link {SERVICE_SRC}`"),
|
||||||
"Run `systemctl link {SOCKET_SRC}`".to_string(),
|
format!("Run `systemctl link {SOCKET_SRC}`"),
|
||||||
"Run `systemctl daemon-reload`".to_string(),
|
"Run `systemctl daemon-reload`".to_string(),
|
||||||
],
|
],
|
||||||
)]
|
)]
|
||||||
|
|
|
@ -52,21 +52,17 @@ impl Action for CreateNixTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
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(
|
vec![ActionDescription::new(
|
||||||
self.tracing_synopsis(),
|
self.tracing_synopsis(),
|
||||||
vec![
|
create_directory_descriptions,
|
||||||
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(", ")
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,22 @@ use std::io::{stdin, stdout, BufRead, Write};
|
||||||
use eyre::{eyre, WrapErr};
|
use eyre::{eyre, WrapErr};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum PromptChoice {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
Explain,
|
||||||
|
}
|
||||||
|
|
||||||
// Do not try to get clever!
|
// 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.
|
// 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
|
// 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 stdout = stdout();
|
||||||
let mut term =
|
let mut term =
|
||||||
term::terminfo::TerminfoTerminal::new(stdout).ok_or(eyre!("Couldn't get terminal"))?;
|
term::terminfo::TerminfoTerminal::new(stdout).ok_or(eyre!("Couldn't get terminal"))?;
|
||||||
|
@ -15,12 +26,34 @@ pub(crate) async fn confirm(question: impl AsRef<str>, default: bool) -> eyre::R
|
||||||
"\
|
"\
|
||||||
{question}\n\
|
{question}\n\
|
||||||
\n\
|
\n\
|
||||||
{are_you_sure} ({yes}/{no}): \
|
{are_you_sure} ({yes}/{no}{maybe_explain}): \
|
||||||
",
|
",
|
||||||
question = question.as_ref(),
|
question = question.as_ref(),
|
||||||
are_you_sure = "Proceed?".bold(),
|
are_you_sure = "Proceed?".bold(),
|
||||||
no = if default { "n" } else { "N" }.red(),
|
no = if default == PromptChoice::No {
|
||||||
yes = if default { "Y" } else { "y" }.green(),
|
"[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())?;
|
term.write_all(with_confirm.as_bytes())?;
|
||||||
|
@ -29,10 +62,11 @@ pub(crate) async fn confirm(question: impl AsRef<str>, default: bool) -> eyre::R
|
||||||
let input = read_line()?;
|
let input = read_line()?;
|
||||||
|
|
||||||
let r = match &*input.to_lowercase() {
|
let r = match &*input.to_lowercase() {
|
||||||
"y" | "yes" => true,
|
"y" | "yes" => PromptChoice::Yes,
|
||||||
"n" | "no" => false,
|
"n" | "no" => PromptChoice::No,
|
||||||
|
"e" | "explain" => PromptChoice::Explain,
|
||||||
"" => default,
|
"" => default,
|
||||||
_ => false,
|
_ => PromptChoice::No,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(r)
|
Ok(r)
|
||||||
|
|
|
@ -6,7 +6,11 @@ use std::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::ActionState,
|
action::ActionState,
|
||||||
cli::{ensure_root, interaction, signal_channel, CommandExecute},
|
cli::{
|
||||||
|
ensure_root,
|
||||||
|
interaction::{self, PromptChoice},
|
||||||
|
signal_channel, CommandExecute,
|
||||||
|
},
|
||||||
error::HasExpectedErrors,
|
error::HasExpectedErrors,
|
||||||
plan::RECEIPT_LOCATION,
|
plan::RECEIPT_LOCATION,
|
||||||
planner::Planner,
|
planner::Planner,
|
||||||
|
@ -144,15 +148,23 @@ impl CommandExecute for Install {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !no_confirm {
|
if !no_confirm {
|
||||||
if !interaction::confirm(
|
let mut currently_explaining = explain;
|
||||||
|
loop {
|
||||||
|
match interaction::prompt(
|
||||||
install_plan
|
install_plan
|
||||||
.describe_install(explain)
|
.describe_install(currently_explaining)
|
||||||
.map_err(|e| eyre!(e))?,
|
.map_err(|e| eyre!(e))?,
|
||||||
true,
|
PromptChoice::Yes,
|
||||||
|
currently_explaining,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").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());
|
eprintln!("{}", "Installation failure, offering to revert...".red());
|
||||||
if !interaction::confirm(
|
let mut currently_explaining = explain;
|
||||||
|
loop {
|
||||||
|
match interaction::prompt(
|
||||||
install_plan
|
install_plan
|
||||||
.describe_uninstall(explain)
|
.describe_uninstall(currently_explaining)
|
||||||
.map_err(|e| eyre!(e))?,
|
.map_err(|e| eyre!(e))?,
|
||||||
true,
|
PromptChoice::Yes,
|
||||||
|
currently_explaining,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!")
|
PromptChoice::Yes => break,
|
||||||
.await;
|
PromptChoice::Explain => currently_explaining = true,
|
||||||
|
PromptChoice::No => {
|
||||||
|
interaction::clean_exit_with_message(
|
||||||
|
"Okay, didn't do anything! Bye!",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let rx2 = tx.subscribe();
|
let rx2 = tx.subscribe();
|
||||||
let res = install_plan.uninstall(rx2).await;
|
let res = install_plan.uninstall(rx2).await;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{ensure_root, signal_channel},
|
cli::{ensure_root, interaction::PromptChoice, signal_channel},
|
||||||
error::HasExpectedErrors,
|
error::HasExpectedErrors,
|
||||||
plan::RECEIPT_LOCATION,
|
plan::RECEIPT_LOCATION,
|
||||||
InstallPlan,
|
InstallPlan,
|
||||||
|
@ -127,13 +127,22 @@ impl CommandExecute for Uninstall {
|
||||||
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
|
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
|
||||||
|
|
||||||
if !no_confirm {
|
if !no_confirm {
|
||||||
if !interaction::confirm(
|
let mut currently_explaining = explain;
|
||||||
plan.describe_uninstall(explain).map_err(|e| eyre!(e))?,
|
loop {
|
||||||
true,
|
match interaction::prompt(
|
||||||
|
plan.describe_uninstall(currently_explaining)
|
||||||
|
.map_err(|e| eyre!(e))?,
|
||||||
|
PromptChoice::Yes,
|
||||||
|
currently_explaining,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
PromptChoice::Yes => break,
|
||||||
|
PromptChoice::Explain => currently_explaining = true,
|
||||||
|
PromptChoice::No => {
|
||||||
|
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,15 +66,10 @@ impl InstallPlan {
|
||||||
\n\
|
\n\
|
||||||
{plan_settings}\n\
|
{plan_settings}\n\
|
||||||
\n\
|
\n\
|
||||||
The following actions will be taken{maybe_explain}:\n\
|
The following actions will be taken:\n\
|
||||||
\n\
|
\n\
|
||||||
{actions}\n\
|
{actions}\n\
|
||||||
",
|
",
|
||||||
maybe_explain = if !explain {
|
|
||||||
" (`--explain` for more context)"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
},
|
|
||||||
planner = planner.typetag_name(),
|
planner = planner.typetag_name(),
|
||||||
plan_settings = planner
|
plan_settings = planner
|
||||||
.settings()?
|
.settings()?
|
||||||
|
|
Loading…
Reference in a new issue