Skip to content

Commit 29f4862

Browse files
committed
feat: add notification support to --ask flags
Change-Id: I6a6a69642ac3443525df583fd200827414801d41
1 parent 461c6d8 commit 29f4862

File tree

9 files changed

+202
-70
lines changed

9 files changed

+202
-70
lines changed

src/clean.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ use tracing::{Level, debug, info, instrument, span, warn};
1919
use crate::{
2020
Result,
2121
commands::{Command, ElevationStrategy},
22-
interface,
22+
interface::{self, NotifyAskMode},
23+
notify::NotificationSender,
2324
};
2425

2526
// Nix impl:
@@ -306,12 +307,32 @@ impl interface::CleanMode {
306307
}
307308

308309
// Clean the paths
309-
if args.ask
310-
&& !Confirm::new("Confirm the cleanup plan?")
311-
.with_default(false)
312-
.prompt()?
313-
{
314-
bail!("User rejected the cleanup plan");
310+
if let Some(ask) = args.ask.as_ref() {
311+
let confirmation = match ask {
312+
NotifyAskMode::Prompt => {
313+
Confirm::new("Confirm the cleanup plan?")
314+
.with_default(false)
315+
.prompt()?
316+
},
317+
NotifyAskMode::Notify => {
318+
let clean_mode = match self {
319+
Self::Profile(_) => "profile",
320+
Self::All(_) => "all",
321+
Self::User(_) => "user",
322+
};
323+
324+
NotificationSender::new(
325+
&format!("nh clean {clean_mode}"),
326+
"Confirm the cleanup plan?",
327+
)
328+
.ask()
329+
},
330+
NotifyAskMode::Both => unimplemented!(),
331+
};
332+
333+
if !confirmation {
334+
bail!("User rejected the cleanup plan");
335+
}
315336
}
316337

317338
if !args.dry {

src/commands.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,24 @@ use thiserror::Error;
1515
use tracing::{debug, info, warn};
1616
use which::which;
1717

18-
use crate::{
19-
installable::Installable,
20-
interface::NixBuildPassthroughArgs,
21-
};
18+
use crate::{installable::Installable, interface::NixBuildPassthroughArgs};
2219

2320
static PASSWORD_CACHE: OnceLock<Mutex<HashMap<String, SecretString>>> =
2421
OnceLock::new();
2522

2623
fn get_cached_password(host: &str) -> Option<SecretString> {
2724
let cache = PASSWORD_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
28-
let guard = cache.lock().unwrap_or_else(|e| e.into_inner());
25+
let guard = cache
26+
.lock()
27+
.unwrap_or_else(std::sync::PoisonError::into_inner);
2928
guard.get(host).cloned()
3029
}
3130

3231
fn cache_password(host: &str, password: SecretString) {
3332
let cache = PASSWORD_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
34-
let mut guard = cache.lock().unwrap_or_else(|e| e.into_inner());
33+
let mut guard = cache
34+
.lock()
35+
.unwrap_or_else(std::sync::PoisonError::into_inner);
3536
guard.insert(host.to_string(), password);
3637
}
3738

@@ -452,7 +453,7 @@ impl Command {
452453
Some(cached_password)
453454
} else {
454455
let password =
455-
inquire::Password::new(&format!("[sudo] password for {}:", host))
456+
inquire::Password::new(&format!("[sudo] password for {host}:"))
456457
.without_confirmation()
457458
.prompt()
458459
.context("Failed to read sudo password")?;
@@ -495,11 +496,11 @@ impl Command {
495496
for (key, action) in &self.env_vars {
496497
match action {
497498
EnvAction::Set(value) => {
498-
elev_cmd = elev_cmd.arg(format!("{}={}", key, value));
499+
elev_cmd = elev_cmd.arg(format!("{key}={value}"));
499500
},
500501
EnvAction::Preserve => {
501502
if let Ok(value) = std::env::var(key) {
502-
elev_cmd = elev_cmd.arg(format!("{}={}", key, value));
503+
elev_cmd = elev_cmd.arg(format!("{key}={value}"));
503504
}
504505
},
505506
_ => {},

src/darwin.rs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
use std::{env, path::PathBuf};
1+
use std::{env, fmt, path::PathBuf};
22

33
use color_eyre::eyre::{Context, bail, eyre};
44
use tracing::{debug, warn};
55

66
use crate::{
77
Result,
8-
commands,
9-
commands::{Command, ElevationStrategy},
8+
commands::{self, Command, ElevationStrategy},
109
installable::Installable,
1110
interface::{
1211
DarwinArgs,
1312
DarwinRebuildArgs,
1413
DarwinReplArgs,
1514
DarwinSubcommand,
1615
DiffType,
16+
NotifyAskMode,
1717
},
1818
nixos::toplevel_for,
19+
notify::NotificationSender,
1920
update::update,
2021
util::{get_hostname, print_dix_diff},
2122
};
@@ -34,7 +35,7 @@ impl DarwinArgs {
3435
match self.subcommand {
3536
DarwinSubcommand::Switch(args) => args.rebuild(&Switch, elevation),
3637
DarwinSubcommand::Build(args) => {
37-
if args.common.ask || args.common.dry {
38+
if args.common.ask.is_some() || args.common.dry {
3839
warn!("`--ask` and `--dry` have no effect for `nh darwin build`");
3940
}
4041
args.rebuild(&Build, elevation)
@@ -49,6 +50,16 @@ enum DarwinRebuildVariant {
4950
Build,
5051
}
5152

53+
impl fmt::Display for DarwinRebuildVariant {
54+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55+
let s = match self {
56+
DarwinRebuildVariant::Build => "build",
57+
DarwinRebuildVariant::Switch => "switch",
58+
};
59+
write!(f, "{s}")
60+
}
61+
}
62+
5263
impl DarwinRebuildArgs {
5364
fn rebuild(
5465
self,
@@ -144,13 +155,29 @@ impl DarwinRebuildArgs {
144155
let _ = print_dix_diff(&PathBuf::from(CURRENT_PROFILE), &target_profile);
145156
}
146157

147-
if self.common.ask && !self.common.dry && !matches!(variant, Build) {
148-
let confirmation = inquire::Confirm::new("Apply the config?")
149-
.with_default(false)
150-
.prompt()?;
158+
if !self.common.dry && !matches!(variant, Build) {
159+
if let Some(ask) = self.common.ask {
160+
let confirmation = match ask {
161+
NotifyAskMode::Prompt => {
162+
inquire::Confirm::new("Apply the config?")
163+
.with_default(false)
164+
.prompt()?
165+
},
166+
// MacOS doesn't support notification actions
167+
NotifyAskMode::Notify | NotifyAskMode::Both => {
168+
NotificationSender::new(&format!("nh darwin {variant}"), "testing")
169+
.send()
170+
.unwrap();
171+
172+
inquire::Confirm::new("Apply the config?")
173+
.with_default(false)
174+
.prompt()?
175+
},
176+
};
151177

152-
if !confirmation {
153-
bail!("User rejected the new config");
178+
if !confirmation {
179+
bail!("User rejected the new config");
180+
}
154181
}
155182
}
156183

src/generations.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,11 @@ pub fn print_info(
339339
.iter()
340340
.map(|f| {
341341
let (name, width) = f.column_info(widths);
342-
format!("{:<width$}", name)
342+
format!("{name:<width$}")
343343
})
344344
.collect::<Vec<String>>()
345345
.join(" ");
346-
println!("{}", header);
346+
println!("{header}");
347347

348348
// Print generations in descending order
349349
for generation in generations.iter().rev() {
@@ -382,11 +382,11 @@ pub fn print_info(
382382
Field::Spec => specialisations.clone(),
383383
Field::Size => generation.closure_size.clone(),
384384
};
385-
format!("{:width$}", cell_content)
385+
format!("{cell_content:width$}")
386386
})
387387
.collect::<Vec<String>>()
388388
.join(" ");
389-
println!("{}", row);
389+
println!("{row}");
390390
}
391391

392392
Ok(())

src/home.rs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{env, ffi::OsString, path::PathBuf};
1+
use std::{env, ffi::OsString, fmt, path::PathBuf};
22

33
use color_eyre::{
44
Result,
@@ -7,10 +7,17 @@ use color_eyre::{
77
use tracing::{debug, info, warn};
88

99
use crate::{
10-
commands,
11-
commands::Command,
10+
commands::{self, Command},
1211
installable::Installable,
13-
interface::{self, DiffType, HomeRebuildArgs, HomeReplArgs, HomeSubcommand},
12+
interface::{
13+
self,
14+
DiffType,
15+
HomeRebuildArgs,
16+
HomeReplArgs,
17+
HomeSubcommand,
18+
NotifyAskMode,
19+
},
20+
notify::NotificationSender,
1421
update::update,
1522
util::{get_hostname, print_dix_diff},
1623
};
@@ -26,7 +33,7 @@ impl interface::HomeArgs {
2633
match self.subcommand {
2734
HomeSubcommand::Switch(args) => args.rebuild(&Switch),
2835
HomeSubcommand::Build(args) => {
29-
if args.common.ask || args.common.dry {
36+
if args.common.ask.is_some() || args.common.dry {
3037
warn!("`--ask` and `--dry` have no effect for `nh home build`");
3138
}
3239
args.rebuild(&Build)
@@ -42,6 +49,16 @@ enum HomeRebuildVariant {
4249
Switch,
4350
}
4451

52+
impl fmt::Display for HomeRebuildVariant {
53+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54+
let s = match self {
55+
HomeRebuildVariant::Build => "build",
56+
HomeRebuildVariant::Switch => "switch",
57+
};
58+
write!(f, "{s}")
59+
}
60+
}
61+
4562
impl HomeRebuildArgs {
4663
fn rebuild(self, variant: &HomeRebuildVariant) -> Result<()> {
4764
use HomeRebuildVariant::Build;
@@ -150,16 +167,24 @@ impl HomeRebuildArgs {
150167
}
151168

152169
if self.common.dry || matches!(variant, Build) {
153-
if self.common.ask {
170+
if self.common.ask.is_some() {
154171
warn!("--ask has no effect as dry run was requested");
155172
}
156173
return Ok(());
157174
}
158175

159-
if self.common.ask {
160-
let confirmation = inquire::Confirm::new("Apply the config?")
161-
.with_default(false)
162-
.prompt()?;
176+
if let Some(ask) = self.common.ask {
177+
let confirmation = match ask {
178+
NotifyAskMode::Prompt => {
179+
inquire::Confirm::new("Apply the config?")
180+
.with_default(false)
181+
.prompt()?
182+
},
183+
NotifyAskMode::Notify => {
184+
NotificationSender::new("nh os rollback", "testing").ask()
185+
},
186+
NotifyAskMode::Both => unimplemented!(),
187+
};
163188

164189
if !confirmation {
165190
bail!("User rejected the new config");

src/interface.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,25 @@ pub enum DiffType {
256256
Never,
257257
}
258258

259+
#[derive(Debug, Clone, ValueEnum, PartialEq)]
260+
pub enum NotifyAskMode {
261+
/// Ask in the terminal (stdin prompt)
262+
Prompt,
263+
/// Ask via a desktop notification action
264+
Notify,
265+
/// Show both notification and terminal prompt (fallback-safe)
266+
Both,
267+
}
268+
259269
#[derive(Debug, Args)]
260270
pub struct OsRollbackArgs {
261271
/// Only print actions, without performing them
262272
#[arg(long, short = 'n')]
263273
pub dry: bool,
264274

265275
/// Ask for confirmation
266-
#[arg(long, short)]
267-
pub ask: bool,
276+
#[arg(long, short, value_enum, default_missing_value = "prompt", num_args = 0..=1)]
277+
pub ask: Option<NotifyAskMode>,
268278

269279
/// Explicitly select some specialisation
270280
#[arg(long, short)]
@@ -295,8 +305,8 @@ pub struct CommonRebuildArgs {
295305
pub dry: bool,
296306

297307
/// Ask for confirmation
298-
#[arg(long, short)]
299-
pub ask: bool,
308+
#[arg(long, short, default_missing_value = "prompt", num_args = 0..=1)]
309+
pub ask: Option<NotifyAskMode>,
300310

301311
#[command(flatten)]
302312
pub installable: Installable,
@@ -427,8 +437,8 @@ pub struct CleanArgs {
427437
pub dry: bool,
428438

429439
/// Ask for confirmation
430-
#[arg(long, short)]
431-
pub ask: bool,
440+
#[arg(long, short, default_missing_value = "prompt", num_args = 0..=1)]
441+
pub ask: Option<NotifyAskMode>,
432442

433443
/// Don't run nix store --gc
434444
#[arg(long = "no-gc", alias = "nogc")]

src/logging.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub fn setup_logging(
9393
macro_rules! nh_trace {
9494
($($arg:tt)*) => {
9595
use notify_rust::Urgency;
96-
use crate::notify::NotificationSender;
96+
use $crate::notify::NotificationSender;
9797
let message = format!($($arg)*);
9898
tracing::trace!($($arg)*);
9999
NotificationSender::new("nh trace", &message).urgency(Urgency::Low).send().unwrap();
@@ -104,7 +104,7 @@ macro_rules! nh_trace {
104104
macro_rules! nh_debug {
105105
($($arg:tt)*) => {
106106
use notify_rust::Urgency;
107-
use crate::notify::NotificationSender;
107+
use $crate::notify::NotificationSender;
108108

109109
let message = format!($($arg)*);
110110
tracing::debug!($($arg)*);
@@ -116,7 +116,7 @@ macro_rules! nh_debug {
116116
macro_rules! nh_info {
117117
($($arg:tt)*) => {
118118
use notify_rust::Urgency;
119-
use crate::notify::NotificationSender;
119+
use $crate::notify::NotificationSender;
120120
let message = format!($($arg)*);
121121
tracing::info!($($arg)*);
122122
NotificationSender::new("nh info", &message).urgency(Urgency::Normal).send().unwrap();
@@ -127,10 +127,9 @@ macro_rules! nh_info {
127127
macro_rules! nh_warn {
128128
($($arg:tt)*) => {
129129
use notify_rust::Urgency;
130-
use crate::notify::NotificationSender;
130+
use $crate::notify::NotificationSender;
131131
let message = format!($($arg)*);
132132
tracing::warn!($($arg)*);
133133
NotificationSender::new("nh warn", &message).urgency(Urgency::Normal).send().unwrap();
134134
};
135135
}
136-

0 commit comments

Comments
 (0)