IT WORKS
This commit is contained in:
parent
da509d97c7
commit
3765e918d6
18 changed files with 348 additions and 226 deletions
34
src/args.rs
34
src/args.rs
|
|
@ -1,4 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
use std::{
|
||||
env,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
use clap::ColorChoice;
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ impl FromStr for NixOsOption {
|
|||
pub struct AppendCmd {
|
||||
#[arg(required = true)]
|
||||
pub name: Arc<str>,
|
||||
|
||||
#[arg(required = true)]
|
||||
pub value: Arc<str>,
|
||||
}
|
||||
|
|
@ -67,6 +71,28 @@ pub enum Subcommand {
|
|||
Delta(DeltaCmd),
|
||||
}
|
||||
|
||||
static DEFAULT_PATH: LazyLock<Box<OsStr>> = LazyLock::new(|| {
|
||||
// This has to be in a let binding to keep the storage around.
|
||||
let nixos_config = env::var_os("NIXOS_CONFIG");
|
||||
let nixos_config = nixos_config
|
||||
.as_deref()
|
||||
.map(Path::new)
|
||||
.unwrap_or(Path::new("/etc/nixos/configuration.nix"));
|
||||
|
||||
nixos_config
|
||||
.parent()
|
||||
.unwrap_or_else(|| {
|
||||
error!(
|
||||
"Your $NIXOS_CONFIG value doesn't make sense: {}. Ignoring.",
|
||||
nixos_config.display(),
|
||||
);
|
||||
Path::new("/etc/nixos")
|
||||
})
|
||||
.join("dynamic.nix")
|
||||
.into_os_string()
|
||||
.into_boxed_os_str()
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, clap::Parser)]
|
||||
#[command(version, about, author)]
|
||||
#[command(arg_required_else_help(true), args_override_self(true))]
|
||||
|
|
@ -75,8 +101,10 @@ pub struct Args {
|
|||
#[arg(long, global(true), default_value = "auto")]
|
||||
pub color: ColorChoice,
|
||||
|
||||
// FIXME: default to /etc/configuration.nix, or something?
|
||||
#[arg(long, global(true), default_value = "./configuration.nix")]
|
||||
/// The .nix file with dynamic overrides to modify.
|
||||
/// [default: $(dirname ${NIXOS_CONFIG-/etc/nixos/configuration.nix})/dynamic.nix]
|
||||
#[arg(long, global(true), default_value = &**DEFAULT_PATH)]
|
||||
#[arg(hide_default_value(true))]
|
||||
pub file: Arc<OsStr>,
|
||||
|
||||
#[command(subcommand)]
|
||||
|
|
|
|||
110
src/lib.rs
110
src/lib.rs
|
|
@ -1,4 +1,7 @@
|
|||
use std::{iter, sync::Arc};
|
||||
use std::{
|
||||
iter,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
pub(crate) mod prelude {
|
||||
#![allow(unused_imports)]
|
||||
|
|
@ -41,14 +44,38 @@ pub mod line;
|
|||
mod nixcmd;
|
||||
pub use line::Line;
|
||||
pub mod source;
|
||||
pub use source::SourceLine;
|
||||
pub use source::{SourceFile, SourceLine};
|
||||
|
||||
#[cfg(all(not(feature = "regex-full"), not(feature = "regex-lite")))]
|
||||
compile_error!("At least one of features `regex-full` or `regex-lite` must be used");
|
||||
|
||||
#[cfg(feature = "regex-full")]
|
||||
use regex as _regex;
|
||||
|
||||
// Having both `regex-full` and `regex-lite` isn't an error; it's just wasteful.
|
||||
#[cfg(not(feature = "regex-full"))]
|
||||
use regex_lite as _regex;
|
||||
|
||||
use _regex::Regex;
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::source::SourceFile;
|
||||
|
||||
pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' '];
|
||||
|
||||
/// Regex pattern to extract the priority in a `lib.mkOverride` call.
|
||||
static MK_OVERRIDE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
// Named capture group: priority
|
||||
// - Word boundary
|
||||
// - Literal `mkOverride`
|
||||
// - One or more whitespace characters
|
||||
// - Literal open parenthesis
|
||||
// - Named capture group "priority"
|
||||
// - One or more of: digit characters, or literal `-`
|
||||
// - Literal close parenthesis
|
||||
Regex::new(r"(?-u)\bmkOverride\s+\((?<priority>[\d-]+)\)").unwrap()
|
||||
});
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn do_delta(args: Arc<Args>, delta_args: DeltaCmd) -> Result<(), BoxDynError> {
|
||||
todo!();
|
||||
|
|
@ -65,17 +92,15 @@ pub fn do_append(args: Arc<Args>, append_args: AppendCmd) -> Result<(), BoxDynEr
|
|||
filepath.to_path_buf()
|
||||
};
|
||||
|
||||
// Get what file that thing is defined in.
|
||||
let def_path = get_where(&append_args.name, &filepath)?;
|
||||
|
||||
let mut opts = File::options();
|
||||
opts.read(true)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.custom_flags(libc::O_CLOEXEC);
|
||||
let source_file = SourceFile::open_from(Arc::from(def_path), opts)?;
|
||||
|
||||
let pri = get_highest_prio(&append_args.name, source_file.clone())?;
|
||||
let source_file = SourceFile::open_from(Arc::from(filepath), opts)?;
|
||||
let pri = get_where(source_file.clone())?;
|
||||
|
||||
let new_pri = pri - 1;
|
||||
|
||||
let new_pri_line = get_next_prio_line(
|
||||
|
|
@ -85,7 +110,7 @@ pub fn do_append(args: Arc<Args>, append_args: AppendCmd) -> Result<(), BoxDynEr
|
|||
append_args.value.into(),
|
||||
)?;
|
||||
|
||||
eprintln!("new_pri_line={new_pri_line}");
|
||||
debug!("new_pri_line={new_pri_line}");
|
||||
|
||||
write_next_prio(source_file, new_pri_line)?;
|
||||
|
||||
|
|
@ -108,35 +133,30 @@ pub fn expr_for_configuration(source_file: &Path) -> OsString {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path>, BoxDynError> {
|
||||
let expr = expr_for_configuration(configuration_nix);
|
||||
let attrpath = format!("options.{}.definitionsWithLocations", option_name);
|
||||
|
||||
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||
.into_command()
|
||||
.output_checked_utf8()?;
|
||||
let stdout = output.stdout();
|
||||
|
||||
let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?;
|
||||
let last_location = definitions.into_iter().last().unwrap();
|
||||
|
||||
Ok(Box::from(last_location.file))
|
||||
fn maybe_extract_prio_from_line(line: &SourceLine) -> Option<i64> {
|
||||
MK_OVERRIDE_RE
|
||||
.captures(line.text_ref())
|
||||
.map(|caps| caps.name("priority").unwrap().as_str())
|
||||
.map(|prio_str| {
|
||||
i64::from_str(prio_str).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"lib.mkOverride called with non-integer {}: {}. Nix source code is wrong!\n{}",
|
||||
prio_str, e, line,
|
||||
);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result<i64, BoxDynError> {
|
||||
// Get the current highest priority.
|
||||
pub fn get_where(dynamic_nix: SourceFile) -> Result<i64, BoxDynError> {
|
||||
let lines = dynamic_nix.lines()?;
|
||||
let prio = lines
|
||||
.into_iter()
|
||||
.filter_map(maybe_extract_prio_from_line)
|
||||
.sorted_unstable()
|
||||
.next() // Priorities with lower integer values are "stronger" priorities.
|
||||
.unwrap_or(0);
|
||||
|
||||
let expr = expr_for_configuration(&source.path());
|
||||
|
||||
// Get the highest priority, and the file its defined in.
|
||||
let attrpath = format!("options.{}.highestPrio", option_name);
|
||||
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||
.into_command()
|
||||
.output_checked_utf8()?;
|
||||
let stdout = output.stdout();
|
||||
let highest_prio = i64::from_str(stdout.trim())?;
|
||||
|
||||
Ok(highest_prio)
|
||||
Ok(prio)
|
||||
}
|
||||
|
||||
pub fn get_next_prio_line(
|
||||
|
|
@ -146,15 +166,19 @@ pub fn get_next_prio_line(
|
|||
new_value: Arc<str>,
|
||||
) -> Result<SourceLine, BoxDynError> {
|
||||
let source_lines = source.lines()?;
|
||||
let last_line = source_lines.last();
|
||||
assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]"));
|
||||
let last_line = last_line.unwrap();
|
||||
let penultimate = source_lines.get(source_lines.len() - 2);
|
||||
// FIXME: don't rely on whitespace lol
|
||||
debug_assert_eq!(penultimate.map(SourceLine::text).as_deref(), Some(" ];"));
|
||||
let penultimate = penultimate.unwrap();
|
||||
|
||||
let new_generation = 0 - new_prio;
|
||||
|
||||
let new_line = SourceLine {
|
||||
line: last_line.line,
|
||||
line: penultimate.line,
|
||||
path: source.path(),
|
||||
text: Arc::from(format!(
|
||||
" {option_name} = lib.mkOverride ({new_prio}) ({new_value});",
|
||||
" {} = lib.mkOverride ({}) ({}); # DYNIX GENERATION {}",
|
||||
option_name, new_prio, new_value, new_generation,
|
||||
)),
|
||||
};
|
||||
|
||||
|
|
@ -165,12 +189,12 @@ pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(
|
|||
let new_mod_start = SourceLine {
|
||||
line: new_line.line.prev(),
|
||||
path: source.path(),
|
||||
text: Arc::from(" {"),
|
||||
text: Arc::from(" {"),
|
||||
};
|
||||
let new_mod_end = SourceLine {
|
||||
line: new_line.line.next(),
|
||||
path: source.path(),
|
||||
text: Arc::from(" }"),
|
||||
text: Arc::from(" }"),
|
||||
};
|
||||
|
||||
source.insert_lines(&[new_mod_start, new_line, new_mod_end])?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue