Skip to content

Commit f4c023c

Browse files
committed
feat: add {os,home,darwin} edit
1 parent 6cd62b0 commit f4c023c

File tree

5 files changed

+319
-4
lines changed

5 files changed

+319
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ functionality, under the "Removed" section.
4646
- It's roughly %4 faster according to testing, but IO is still a limiting
4747
factor and results may differ.
4848
- Added more context to some minor debug messages across platform commands.
49+
- Added `nh {os,home,darwin} edit` subcommand.
4950

5051
### Fixed
5152

src/darwin.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use crate::Result;
88
use crate::commands;
99
use crate::commands::Command;
1010
use crate::installable::Installable;
11-
use crate::interface::{DarwinArgs, DarwinRebuildArgs, DarwinReplArgs, DarwinSubcommand, DiffType};
11+
use crate::interface::{
12+
DarwinArgs, DarwinEditArgs, DarwinRebuildArgs, DarwinReplArgs, DarwinSubcommand, DiffType,
13+
};
1214
use crate::nixos::toplevel_for;
1315
use crate::update::update;
1416
use crate::util::{get_hostname, print_dix_diff};
@@ -33,6 +35,7 @@ impl DarwinArgs {
3335
args.rebuild(&Build)
3436
}
3537
DarwinSubcommand::Repl(args) => args.run(),
38+
DarwinSubcommand::Edit(args) => args.run(),
3639
}
3740
}
3841
}
@@ -231,3 +234,69 @@ impl DarwinReplArgs {
231234
Ok(())
232235
}
233236
}
237+
238+
impl DarwinEditArgs {
239+
fn run(self) -> Result<()> {
240+
let editor = env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
241+
242+
// Use NH_DARWIN_FLAKE if available, otherwise use the provided installable
243+
let target_installable = if let Ok(darwin_flake) = env::var("NH_DARWIN_FLAKE") {
244+
debug!("Using NH_DARWIN_FLAKE: {}", darwin_flake);
245+
246+
let mut elems = darwin_flake.splitn(2, '#');
247+
let reference = match elems.next() {
248+
Some(r) => r.to_owned(),
249+
None => return Err(eyre!("NH_DARWIN_FLAKE missing reference part")),
250+
};
251+
let attribute = elems
252+
.next()
253+
.map(crate::installable::parse_attribute)
254+
.unwrap_or_default();
255+
256+
Installable::Flake {
257+
reference,
258+
attribute,
259+
}
260+
} else {
261+
self.installable
262+
};
263+
264+
match target_installable {
265+
Installable::File { path, .. } => {
266+
let flake_path = path.join("flake.nix");
267+
let str_path = flake_path
268+
.to_str()
269+
.ok_or_else(|| eyre!("Edit path is not valid UTF-8"))?;
270+
271+
Command::new(editor)
272+
.arg(str_path)
273+
.with_required_env()
274+
.show_output(true)
275+
.run()?
276+
}
277+
Installable::Flake { reference, .. } => {
278+
Command::new("nix")
279+
.arg("flake")
280+
.arg("prefetch")
281+
.arg("-o")
282+
.arg("result")
283+
.arg(reference)
284+
.with_required_env()
285+
.show_output(true)
286+
.run()?;
287+
288+
Command::new(editor)
289+
.arg("result/flake.nix")
290+
.with_required_env()
291+
.show_output(true)
292+
.run()?;
293+
294+
std::fs::remove_file("result")?
295+
}
296+
Installable::Store { .. } => bail!("Nix doesn't support nix store installables."),
297+
Installable::Expression { .. } => bail!("Nix doesn't support nix expression."),
298+
};
299+
300+
Ok(())
301+
}
302+
}

src/home.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use tracing::{debug, info, warn};
99
use crate::commands;
1010
use crate::commands::Command;
1111
use crate::installable::Installable;
12-
use crate::interface::{self, DiffType, HomeRebuildArgs, HomeReplArgs, HomeSubcommand};
12+
use crate::interface::{
13+
self, DiffType, HomeEditArgs, HomeRebuildArgs, HomeReplArgs, HomeSubcommand,
14+
};
1315
use crate::update::update;
1416
use crate::util::{get_hostname, print_dix_diff};
1517

@@ -30,6 +32,7 @@ impl interface::HomeArgs {
3032
args.rebuild(&Build)
3133
}
3234
HomeSubcommand::Repl(args) => args.run(),
35+
HomeSubcommand::Edit(args) => args.run(),
3336
}
3437
}
3538
}
@@ -398,3 +401,76 @@ impl HomeReplArgs {
398401
Ok(())
399402
}
400403
}
404+
405+
impl HomeEditArgs {
406+
fn run(self) -> Result<()> {
407+
let editor = env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
408+
409+
// Use NH_HOME_FLAKE if available, otherwise use the provided installable
410+
let installable = if let Ok(home_flake) = env::var("NH_HOME_FLAKE") {
411+
debug!("Using NH_HOME_FLAKE: {home_flake}");
412+
413+
let mut elems = home_flake.splitn(2, '#');
414+
let reference = match elems.next() {
415+
Some(r) => r.to_owned(),
416+
None => return Err(eyre!("NH_HOME_FLAKE missing reference part")),
417+
};
418+
let attribute = elems
419+
.next()
420+
.map(crate::installable::parse_attribute)
421+
.unwrap_or_default();
422+
423+
Installable::Flake {
424+
reference,
425+
attribute,
426+
}
427+
} else {
428+
self.installable
429+
};
430+
431+
let toplevel = toplevel_for(
432+
installable,
433+
false,
434+
&self.extra_args,
435+
self.configuration.clone(),
436+
)?;
437+
438+
match toplevel {
439+
Installable::File { path, .. } => {
440+
let flake_path = path.join("flake.nix");
441+
let str_path = flake_path
442+
.to_str()
443+
.ok_or_else(|| eyre!("Edit path is not valid UTF-8"))?;
444+
445+
Command::new(editor)
446+
.arg(str_path)
447+
.with_required_env()
448+
.show_output(true)
449+
.run()?
450+
}
451+
Installable::Flake { reference, .. } => {
452+
Command::new("nix")
453+
.arg("flake")
454+
.arg("prefetch")
455+
.arg("-o")
456+
.arg("result")
457+
.arg(reference)
458+
.with_required_env()
459+
.show_output(true)
460+
.run()?;
461+
462+
Command::new(editor)
463+
.arg("result/flake.nix")
464+
.with_required_env()
465+
.show_output(true)
466+
.run()?;
467+
468+
std::fs::remove_file("result")?
469+
}
470+
Installable::Store { .. } => bail!("Nix doesn't support nix store installables."),
471+
Installable::Expression { .. } => bail!("Nix doesn't support nix expression."),
472+
};
473+
474+
Ok(())
475+
}
476+
}

src/interface.rs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ impl OsArgs {
121121
let is_flake = args.uses_flakes();
122122
Box::new(OsReplFeatures { is_flake })
123123
}
124+
OsSubcommand::Edit(args) => {
125+
if args.uses_flakes() {
126+
Box::new(FlakeFeatures)
127+
} else {
128+
Box::new(LegacyFeatures)
129+
}
130+
}
124131
OsSubcommand::Switch(args)
125132
| OsSubcommand::Boot(args)
126133
| OsSubcommand::Test(args)
@@ -160,6 +167,9 @@ pub enum OsSubcommand {
160167
/// Load system in a repl
161168
Repl(OsReplArgs),
162169

170+
/// Edit NixOS configuration
171+
Edit(OsEditArgs),
172+
163173
/// List available generations from profile path
164174
Info(OsGenerationsArgs),
165175

@@ -316,7 +326,29 @@ impl OsReplArgs {
316326
#[must_use]
317327
pub fn uses_flakes(&self) -> bool {
318328
// Check environment variables first
319-
if env::var("NH_OS_FLAKE").is_ok() {
329+
if env::var("NH_OS_FLAKE").is_ok_and(|v| !v.is_empty()) {
330+
return true;
331+
}
332+
333+
// Check installable type
334+
matches!(self.installable, Installable::Flake { .. })
335+
}
336+
}
337+
338+
#[derive(Debug, Args)]
339+
pub struct OsEditArgs {
340+
#[command(flatten)]
341+
pub installable: Installable,
342+
343+
/// When using a flake installable, select this hostname from nixosConfigurations
344+
#[arg(long, short = 'H', global = true)]
345+
pub hostname: Option<String>,
346+
}
347+
348+
impl OsEditArgs {
349+
pub fn uses_flakes(&self) -> bool {
350+
// Check environment variables first
351+
if env::var("NH_OS_FLAKE").is_ok_and(|v| !v.is_empty()) {
320352
return true;
321353
}
322354

@@ -447,6 +479,13 @@ impl HomeArgs {
447479
let is_flake = args.uses_flakes();
448480
Box::new(HomeReplFeatures { is_flake })
449481
}
482+
HomeSubcommand::Edit(args) => {
483+
if args.uses_flakes() {
484+
Box::new(FlakeFeatures)
485+
} else {
486+
Box::new(LegacyFeatures)
487+
}
488+
}
450489
HomeSubcommand::Switch(args) | HomeSubcommand::Build(args) => {
451490
if args.uses_flakes() {
452491
Box::new(FlakeFeatures)
@@ -468,6 +507,9 @@ pub enum HomeSubcommand {
468507

469508
/// Load a home-manager configuration in a Nix REPL
470509
Repl(HomeReplArgs),
510+
511+
/// Edit home-manager configuration
512+
Edit(HomeEditArgs),
471513
}
472514

473515
#[derive(Debug, Args)]
@@ -543,6 +585,34 @@ impl HomeReplArgs {
543585
}
544586
}
545587

588+
#[derive(Debug, Args)]
589+
pub struct HomeEditArgs {
590+
#[command(flatten)]
591+
pub installable: Installable,
592+
593+
/// Name of the flake homeConfigurations attribute, like username@hostname
594+
///
595+
/// If unspecified, will try <username>@<hostname> and <username>
596+
#[arg(long, short)]
597+
pub configuration: Option<String>,
598+
599+
/// Extra arguments passed to nix repl
600+
#[arg(last = true)]
601+
pub extra_args: Vec<String>,
602+
}
603+
604+
impl HomeEditArgs {
605+
pub fn uses_flakes(&self) -> bool {
606+
// Check environment variables first
607+
if env::var("NH_HOME_FLAKE").is_ok_and(|v| !v.is_empty()) {
608+
return true;
609+
}
610+
611+
// Check installable type
612+
matches!(self.installable, Installable::Flake { .. })
613+
}
614+
}
615+
546616
#[derive(Debug, Parser)]
547617
/// Generate shell completion files into stdout
548618
pub struct CompletionArgs {
@@ -567,6 +637,13 @@ impl DarwinArgs {
567637
let is_flake = args.uses_flakes();
568638
Box::new(DarwinReplFeatures { is_flake })
569639
}
640+
DarwinSubcommand::Edit(args) => {
641+
if args.uses_flakes() {
642+
Box::new(FlakeFeatures)
643+
} else {
644+
Box::new(LegacyFeatures)
645+
}
646+
}
570647
DarwinSubcommand::Switch(args) | DarwinSubcommand::Build(args) => {
571648
if args.uses_flakes() {
572649
Box::new(FlakeFeatures)
@@ -586,6 +663,8 @@ pub enum DarwinSubcommand {
586663
Build(DarwinRebuildArgs),
587664
/// Load a nix-darwin configuration in a Nix REPL
588665
Repl(DarwinReplArgs),
666+
/// Edit nix-darwin configuration
667+
Edit(DarwinEditArgs),
589668
}
590669

591670
#[derive(Debug, Args)]
@@ -645,6 +724,28 @@ impl DarwinReplArgs {
645724
}
646725
}
647726

727+
#[derive(Debug, Args)]
728+
pub struct DarwinEditArgs {
729+
#[command(flatten)]
730+
pub installable: Installable,
731+
732+
/// When using a flake installable, select this hostname from darwinConfigurations
733+
#[arg(long, short = 'H', global = true)]
734+
pub hostname: Option<String>,
735+
}
736+
737+
impl DarwinEditArgs {
738+
pub fn uses_flakes(&self) -> bool {
739+
// Check environment variables first
740+
if env::var("NH_DARWIN_FLAKE").is_ok_and(|v| !v.is_empty()) {
741+
return true;
742+
}
743+
744+
// Check installable type
745+
matches!(self.installable, Installable::Flake { .. })
746+
}
747+
}
748+
648749
#[derive(Debug, Args)]
649750
pub struct UpdateArgs {
650751
#[arg(short = 'u', long = "update", conflicts_with = "update_input")]

0 commit comments

Comments
 (0)