Skip to content

Commit b992185

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

File tree

5 files changed

+370
-4
lines changed

5 files changed

+370
-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: 87 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,86 @@ impl DarwinReplArgs {
231234
Ok(())
232235
}
233236
}
237+
238+
impl DarwinEditArgs {
239+
fn run(self) -> Result<()> {
240+
let editor = if let Ok(var) = env::var("EDITOR") {
241+
var
242+
} else {
243+
bail!("Unset environment variable `EDITOR`.")
244+
};
245+
246+
// Use NH_DARWIN_FLAKE if available, otherwise use the provided installable
247+
let target_installable = if let Ok(darwin_flake) = env::var("NH_DARWIN_FLAKE") {
248+
debug!("Using NH_DARWIN_FLAKE: {}", darwin_flake);
249+
250+
let mut elems = darwin_flake.splitn(2, '#');
251+
let reference = match elems.next() {
252+
Some(r) => r.to_owned(),
253+
None => return Err(eyre!("NH_DARWIN_FLAKE missing reference part")),
254+
};
255+
let attribute = elems
256+
.next()
257+
.map(crate::installable::parse_attribute)
258+
.unwrap_or_default();
259+
260+
Installable::Flake {
261+
reference,
262+
attribute,
263+
}
264+
} else {
265+
self.installable
266+
};
267+
268+
match target_installable {
269+
Installable::File { path, .. } => {
270+
let str_path = path
271+
.to_str()
272+
.ok_or_else(|| eyre!("Edit path is not valid UTF-8"))?;
273+
274+
Command::new(editor)
275+
.arg(str_path)
276+
.with_required_env()
277+
.show_output(true)
278+
.run()?
279+
}
280+
Installable::Flake { reference, .. } => {
281+
match &reference[0..5] {
282+
"path:" => {
283+
// Removes `path:` prefix
284+
let path = &reference[5..];
285+
286+
Command::new(editor)
287+
.arg(path)
288+
.with_required_env()
289+
.show_output(true)
290+
.run()?
291+
}
292+
_ => {
293+
Command::new("nix")
294+
.arg("flake")
295+
.arg("prefetch")
296+
.arg("-o")
297+
.arg("result")
298+
.arg(reference)
299+
.with_required_env()
300+
.show_output(true)
301+
.run()?;
302+
303+
Command::new(editor)
304+
.arg("result")
305+
.with_required_env()
306+
.show_output(true)
307+
.run()?;
308+
309+
std::fs::remove_file("result")?
310+
}
311+
};
312+
}
313+
Installable::Store { .. } => bail!("Nix doesn't support nix store installables."),
314+
Installable::Expression { .. } => bail!("Nix doesn't support nix expression."),
315+
};
316+
317+
Ok(())
318+
}
319+
}

src/home.rs

Lines changed: 94 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,93 @@ impl HomeReplArgs {
398401
Ok(())
399402
}
400403
}
404+
405+
impl HomeEditArgs {
406+
fn run(self) -> Result<()> {
407+
let editor = if let Ok(var) = env::var("EDITOR") {
408+
var
409+
} else {
410+
bail!("Unset environment variable `EDITOR`.")
411+
};
412+
413+
// Use NH_HOME_FLAKE if available, otherwise use the provided installable
414+
let installable = if let Ok(home_flake) = env::var("NH_HOME_FLAKE") {
415+
debug!("Using NH_HOME_FLAKE: {home_flake}");
416+
417+
let mut elems = home_flake.splitn(2, '#');
418+
let reference = match elems.next() {
419+
Some(r) => r.to_owned(),
420+
None => return Err(eyre!("NH_HOME_FLAKE missing reference part")),
421+
};
422+
let attribute = elems
423+
.next()
424+
.map(crate::installable::parse_attribute)
425+
.unwrap_or_default();
426+
427+
Installable::Flake {
428+
reference,
429+
attribute,
430+
}
431+
} else {
432+
self.installable
433+
};
434+
435+
let toplevel = toplevel_for(
436+
installable,
437+
false,
438+
&self.extra_args,
439+
self.configuration.clone(),
440+
)?;
441+
442+
match toplevel {
443+
Installable::File { path, .. } => {
444+
let str_path = path
445+
.to_str()
446+
.ok_or_else(|| eyre!("Edit path is not valid UTF-8"))?;
447+
448+
Command::new(editor)
449+
.arg(str_path)
450+
.with_required_env()
451+
.show_output(true)
452+
.run()?
453+
}
454+
Installable::Flake { reference, .. } => {
455+
match &reference[0..5] {
456+
"path:" => {
457+
// Removes `path:` prefix
458+
let path = &reference[5..];
459+
460+
Command::new(editor)
461+
.arg(path)
462+
.with_required_env()
463+
.show_output(true)
464+
.run()?
465+
}
466+
_ => {
467+
Command::new("nix")
468+
.arg("flake")
469+
.arg("prefetch")
470+
.arg("-o")
471+
.arg("result")
472+
.arg(reference)
473+
.with_required_env()
474+
.show_output(true)
475+
.run()?;
476+
477+
Command::new(editor)
478+
.arg("result")
479+
.with_required_env()
480+
.show_output(true)
481+
.run()?;
482+
483+
std::fs::remove_file("result")?
484+
}
485+
};
486+
}
487+
Installable::Store { .. } => bail!("Nix doesn't support nix store installables."),
488+
Installable::Expression { .. } => bail!("Nix doesn't support nix expression."),
489+
};
490+
491+
Ok(())
492+
}
493+
}

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)