Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ functionality, under the "Removed" section.
- It's roughly %4 faster according to testing, but IO is still a limiting
factor and results may differ.
- Added more context to some minor debug messages across platform commands.
- Added `nh {os,home,darwin} edit` subcommand.

### Fixed

Expand Down
88 changes: 87 additions & 1 deletion src/darwin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use crate::Result;
use crate::commands;
use crate::commands::Command;
use crate::installable::Installable;
use crate::interface::{DarwinArgs, DarwinRebuildArgs, DarwinReplArgs, DarwinSubcommand, DiffType};
use crate::interface::{
DarwinArgs, DarwinEditArgs, DarwinRebuildArgs, DarwinReplArgs, DarwinSubcommand, DiffType,
};
use crate::nixos::toplevel_for;
use crate::update::update;
use crate::util::{get_hostname, print_dix_diff};
Expand All @@ -33,6 +35,7 @@ impl DarwinArgs {
args.rebuild(&Build)
}
DarwinSubcommand::Repl(args) => args.run(),
DarwinSubcommand::Edit(args) => args.run(),
}
}
}
Expand Down Expand Up @@ -231,3 +234,86 @@ impl DarwinReplArgs {
Ok(())
}
}

impl DarwinEditArgs {
fn run(self) -> Result<()> {
let editor = if let Ok(var) = env::var("EDITOR") {
var
} else {
bail!("Unset environment variable `EDITOR`.")
};

// Use NH_DARWIN_FLAKE if available, otherwise use the provided installable
let target_installable = if let Ok(darwin_flake) = env::var("NH_DARWIN_FLAKE") {
debug!("Using NH_DARWIN_FLAKE: {}", darwin_flake);

let mut elems = darwin_flake.splitn(2, '#');
let reference = match elems.next() {
Some(r) => r.to_owned(),
None => return Err(eyre!("NH_DARWIN_FLAKE missing reference part")),
};
let attribute = elems
.next()
.map(crate::installable::parse_attribute)
.unwrap_or_default();

Installable::Flake {
reference,
attribute,
}
} else {
self.installable
};

match target_installable {
Installable::File { path, .. } => {
let str_path = path
.to_str()
.ok_or_else(|| eyre!("Edit path is not valid UTF-8"))?;

Command::new(editor)
.arg(str_path)
.with_required_env()
.show_output(true)
.run()?
}
Installable::Flake { reference, .. } => {
match &reference[0..5] {
"path:" => {
// Removes `path:` prefix
let path = &reference[5..];

Command::new(editor)
.arg(path)
.with_required_env()
.show_output(true)
.run()?
}
_ => {
Command::new("nix")
.arg("flake")
.arg("prefetch")
.arg("-o")
.arg("result")
.arg(reference)
.with_required_env()
.show_output(true)
.run()?;

Command::new(editor)
.arg("result")
.with_required_env()
.show_output(true)
.run()?;

std::fs::remove_file("result")?
}
};
}
Installable::Store { .. } => bail!("Nix doesn't support nix store installables."),
Installable::Expression { .. } => bail!("Nix doesn't support nix expression."),
};

Ok(())
}
}
95 changes: 94 additions & 1 deletion src/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use tracing::{debug, info, warn};
use crate::commands;
use crate::commands::Command;
use crate::installable::Installable;
use crate::interface::{self, DiffType, HomeRebuildArgs, HomeReplArgs, HomeSubcommand};
use crate::interface::{
self, DiffType, HomeEditArgs, HomeRebuildArgs, HomeReplArgs, HomeSubcommand,
};
use crate::update::update;
use crate::util::{get_hostname, print_dix_diff};

Expand All @@ -30,6 +32,7 @@ impl interface::HomeArgs {
args.rebuild(&Build)
}
HomeSubcommand::Repl(args) => args.run(),
HomeSubcommand::Edit(args) => args.run(),
}
}
}
Expand Down Expand Up @@ -398,3 +401,93 @@ impl HomeReplArgs {
Ok(())
}
}

impl HomeEditArgs {
fn run(self) -> Result<()> {
let editor = if let Ok(var) = env::var("EDITOR") {
var
} else {
bail!("Unset environment variable `EDITOR`.")
};

// Use NH_HOME_FLAKE if available, otherwise use the provided installable
let installable = if let Ok(home_flake) = env::var("NH_HOME_FLAKE") {
debug!("Using NH_HOME_FLAKE: {home_flake}");

let mut elems = home_flake.splitn(2, '#');
let reference = match elems.next() {
Some(r) => r.to_owned(),
None => return Err(eyre!("NH_HOME_FLAKE missing reference part")),
};
let attribute = elems
.next()
.map(crate::installable::parse_attribute)
.unwrap_or_default();

Installable::Flake {
reference,
attribute,
}
} else {
self.installable
};

let toplevel = toplevel_for(
installable,
false,
&self.extra_args,
self.configuration.clone(),
)?;

match toplevel {
Installable::File { path, .. } => {
let str_path = path
.to_str()
.ok_or_else(|| eyre!("Edit path is not valid UTF-8"))?;

Command::new(editor)
.arg(str_path)
.with_required_env()
.show_output(true)
.run()?
}
Installable::Flake { reference, .. } => {
match &reference[0..5] {
"path:" => {
// Removes `path:` prefix
let path = &reference[5..];

Command::new(editor)
.arg(path)
.with_required_env()
.show_output(true)
.run()?
}
_ => {
Command::new("nix")
.arg("flake")
.arg("prefetch")
.arg("-o")
.arg("result")
.arg(reference)
.with_required_env()
.show_output(true)
.run()?;

Command::new(editor)
.arg("result")
.with_required_env()
.show_output(true)
.run()?;

std::fs::remove_file("result")?
}
};
}
Installable::Store { .. } => bail!("Nix doesn't support nix store installables."),
Installable::Expression { .. } => bail!("Nix doesn't support nix expression."),
};

Ok(())
}
}
103 changes: 102 additions & 1 deletion src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ impl OsArgs {
let is_flake = args.uses_flakes();
Box::new(OsReplFeatures { is_flake })
}
OsSubcommand::Edit(args) => {
if args.uses_flakes() {
Box::new(FlakeFeatures)
} else {
Box::new(LegacyFeatures)
}
}
OsSubcommand::Switch(args)
| OsSubcommand::Boot(args)
| OsSubcommand::Test(args)
Expand Down Expand Up @@ -160,6 +167,9 @@ pub enum OsSubcommand {
/// Load system in a repl
Repl(OsReplArgs),

/// Edit a NixOS configuration
Edit(OsEditArgs),

/// List available generations from profile path
Info(OsGenerationsArgs),

Expand Down Expand Up @@ -316,7 +326,29 @@ impl OsReplArgs {
#[must_use]
pub fn uses_flakes(&self) -> bool {
// Check environment variables first
if env::var("NH_OS_FLAKE").is_ok() {
if env::var("NH_OS_FLAKE").is_ok_and(|v| !v.is_empty()) {
return true;
}

// Check installable type
matches!(self.installable, Installable::Flake { .. })
}
}

#[derive(Debug, Args)]
pub struct OsEditArgs {
#[command(flatten)]
pub installable: Installable,

/// When using a flake installable, select this hostname from nixosConfigurations
#[arg(long, short = 'H', global = true)]
pub hostname: Option<String>,
}

impl OsEditArgs {
pub fn uses_flakes(&self) -> bool {
// Check environment variables first
if env::var("NH_OS_FLAKE").is_ok_and(|v| !v.is_empty()) {
return true;
}

Expand Down Expand Up @@ -447,6 +479,13 @@ impl HomeArgs {
let is_flake = args.uses_flakes();
Box::new(HomeReplFeatures { is_flake })
}
HomeSubcommand::Edit(args) => {
if args.uses_flakes() {
Box::new(FlakeFeatures)
} else {
Box::new(LegacyFeatures)
}
}
HomeSubcommand::Switch(args) | HomeSubcommand::Build(args) => {
if args.uses_flakes() {
Box::new(FlakeFeatures)
Expand All @@ -468,6 +507,9 @@ pub enum HomeSubcommand {

/// Load a home-manager configuration in a Nix REPL
Repl(HomeReplArgs),

/// Edit a home-manager configuration
Edit(HomeEditArgs),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -543,6 +585,34 @@ impl HomeReplArgs {
}
}

#[derive(Debug, Args)]
pub struct HomeEditArgs {
#[command(flatten)]
pub installable: Installable,

/// Name of the flake homeConfigurations attribute, like username@hostname
///
/// If unspecified, will try <username>@<hostname> and <username>
#[arg(long, short)]
pub configuration: Option<String>,

/// Extra arguments passed to nix repl
#[arg(last = true)]
pub extra_args: Vec<String>,
}

impl HomeEditArgs {
pub fn uses_flakes(&self) -> bool {
// Check environment variables first
if env::var("NH_HOME_FLAKE").is_ok_and(|v| !v.is_empty()) {
return true;
}

// Check installable type
matches!(self.installable, Installable::Flake { .. })
}
}

#[derive(Debug, Parser)]
/// Generate shell completion files into stdout
pub struct CompletionArgs {
Expand All @@ -567,6 +637,13 @@ impl DarwinArgs {
let is_flake = args.uses_flakes();
Box::new(DarwinReplFeatures { is_flake })
}
DarwinSubcommand::Edit(args) => {
if args.uses_flakes() {
Box::new(FlakeFeatures)
} else {
Box::new(LegacyFeatures)
}
}
DarwinSubcommand::Switch(args) | DarwinSubcommand::Build(args) => {
if args.uses_flakes() {
Box::new(FlakeFeatures)
Expand All @@ -586,6 +663,8 @@ pub enum DarwinSubcommand {
Build(DarwinRebuildArgs),
/// Load a nix-darwin configuration in a Nix REPL
Repl(DarwinReplArgs),
/// Edit a nix-darwin configuration
Edit(DarwinEditArgs),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -645,6 +724,28 @@ impl DarwinReplArgs {
}
}

#[derive(Debug, Args)]
pub struct DarwinEditArgs {
#[command(flatten)]
pub installable: Installable,

/// When using a flake installable, select this hostname from darwinConfigurations
#[arg(long, short = 'H', global = true)]
pub hostname: Option<String>,
}

impl DarwinEditArgs {
pub fn uses_flakes(&self) -> bool {
// Check environment variables first
if env::var("NH_DARWIN_FLAKE").is_ok_and(|v| !v.is_empty()) {
return true;
}

// Check installable type
matches!(self.installable, Installable::Flake { .. })
}
}

#[derive(Debug, Args)]
pub struct UpdateArgs {
#[arg(short = 'u', long = "update", conflicts_with = "update_input")]
Expand Down
Loading
Loading