This commit is contained in:
Qyriad 2026-01-27 11:29:10 +01:00
parent d8ac4e157d
commit bcd11513ef
12 changed files with 1042 additions and 5 deletions

75
src/args.rs Normal file
View file

@ -0,0 +1,75 @@
use clap::ColorChoice;
use crate::prelude::*;
//#[derive(Debug, Clone, PartialEq)]
//#[derive(clap::Args)]
//#[group(required = true, multiple = false)]
//pub enum Config
//{
// Flake,
//}
#[derive(Debug, Clone, PartialEq)]
pub struct NixOsOption {
name: String,
value: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NixOptionParseError(pub Box<str>);
impl Display for NixOptionParseError {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl From<String> for NixOptionParseError {
fn from(value: String) -> Self {
Self(value.into_boxed_str())
}
}
impl StdError for NixOptionParseError {}
impl FromStr for NixOsOption {
type Err = NixOptionParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// FIXME: allow escaping equals sign?
let Some(delim) = s.find('=') else {
return Err(format!("equals sign not found in {}", s).into());
};
todo!();
}
}
#[derive(Debug, Clone, PartialEq, clap::Parser)]
#[command(version, about, author, arg_required_else_help(true))]
pub struct Parser {
#[arg(long, default_value = "auto")]
pub color: ColorChoice,
#[arg(long)]
pub file: Box<OsStr>,
#[arg(required = true)]
pub name: Box<str>,
#[arg(required = true)]
pub value: Box<str>,
///// Flakeref to a base configuration to modify.
//#[arg(group = "config", long, default_value("."))]
//#[arg(long, default_value(Some(".")))]
//flake: Option<Option<Box<OsStr>>>,
//
//#[arg(group = "config", long)]
//expr: Option<String>,
}
//impl Parser {
// fn eval_cmd(&self) {
// todo!();
// }
//}

48
src/color.rs Normal file
View file

@ -0,0 +1,48 @@
use std::{
env,
sync::{LazyLock, OnceLock},
};
#[allow(unused_imports)]
use crate::prelude::*;
pub static CLI_ENABLE_COLOR: OnceLock<bool> = OnceLock::new();
fn is_color_reqd() -> bool {
CLI_ENABLE_COLOR.get().copied().unwrap_or(false)
}
fn is_clicolor_forced() -> bool {
env::var("CLICOLOR_FORCE")
.map(|value| {
if value.is_empty() || value == "0" {
false
} else {
true
}
})
.unwrap_or(false)
}
pub static SHOULD_COLOR: LazyLock<bool> = LazyLock::new(|| is_clicolor_forced() || is_color_reqd());
/// Silly wrapper around LazyLock<&'static str> to impl Display.
pub(crate) struct _LazyLockDisplay(LazyLock<&'static str>);
impl Display for _LazyLockDisplay {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
Display::fmt(&*self.0, f)
}
}
pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
SHOULD_COLOR
.then_some("\x1b[36m")
.unwrap_or_default()
}));
pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
SHOULD_COLOR
// C'mon rustfmt, just format it to match ^.
.then_some("\x1b[0m")
.unwrap_or_default()
}));

157
src/lib.rs Normal file
View file

@ -0,0 +1,157 @@
pub(crate) mod prelude {
#![allow(unused_imports)]
pub use std::{
error::Error as StdError,
ffi::{OsStr, OsString},
fmt::{Display, Formatter, Result as FmtResult},
io::{Error as IoError, Read, Seek, SeekFrom},
path::{Path, PathBuf},
process::{Command, ExitCode},
str::FromStr,
};
#[cfg(unix)]
pub use std::os::{
fd::AsRawFd,
unix::ffi::{OsStrExt, OsStringExt},
};
pub type BoxDynError = Box<dyn StdError + Send + Sync + 'static>;
pub use command_error::{CommandExt, OutputLike};
pub use fs_err::File;
#[cfg(unix)]
pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt};
}
use prelude::*;
use std::{
io::{BufRead, BufReader},
sync::Arc,
};
pub mod args;
pub use args::Parser;
mod color;
pub use color::{CLI_ENABLE_COLOR, SHOULD_COLOR};
pub mod line;
pub use line::Line;
pub mod source;
pub use source::SourceLine;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub struct DefinitionWithLocation {
pub file: Box<Path>,
pub value: Box<str>,
}
pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path>, BoxDynError> {
let expr: OsString = [
// foo
OsStr::new("import <nixpkgs/nixos> { configuration = "),
configuration_nix.as_os_str(),
OsStr::new("; }"),
]
.into_iter()
.map(ToOwned::to_owned)
.collect();
let result = Command::new("nix-instantiate")
.arg("--eval")
.arg("--json")
.arg("--strict")
.arg("--expr")
.arg(expr)
.arg("-A")
.arg(format!("options.{}.definitionsWithLocations", option_name))
.output_checked_utf8()?;
let stdout = result.stdout();
let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?;
let last_location = definitions.last().unwrap();
let file = &*last_location.file;
dbg!(&file);
Ok(Box::from(file))
}
pub fn get_highest_prio(
option_name: &str,
file_with_definition: &Path,
) -> Result<SourceLine, BoxDynError> {
let mut file = File::options()
.read(true)
.write(true)
.create(false)
.custom_flags(libc::O_CLOEXEC)
.open(file_with_definition)?;
// TODO: seek and read backwards.
let mut lines = BufReader::new(&mut file)
.lines()
.enumerate()
.map(|(index, line_res)| {
line_res.map(|line| SourceLine {
line: Line::from_index(index as u64),
path: Arc::from(file_with_definition),
text: Arc::from(line),
})
})
.collect::<Result<Vec<SourceLine>, IoError>>()?;
lines.reverse();
let lines_reversed = lines;
// Get the current highest priority.
let expr: OsString = [
OsStr::new("import <nixpkgs/nixos> { configuration = "),
file_with_definition.as_os_str(),
OsStr::new("; }"),
]
.into_iter()
.map(ToOwned::to_owned)
.collect();
// Get the highest priority, and the file its defined in.
let result = Command::new("nix-instantiate")
.arg("--eval")
.arg("--json")
.arg("--strict")
.arg("--expr")
.arg(expr)
.arg("-A")
.arg(format!("options.{}.highestPrio", option_name))
.output_checked_utf8()?;
let stdout = result.stdout();
let highest_prio = stdout.trim();
let needle = format!("lib.mkOverride ({})", highest_prio);
eprintln!("looking for {needle:?}");
let line_with_current_highest = lines_reversed
.iter()
.position(|line| {
eprintln!("looking for {needle} in {line}");
line.text.contains(&needle)
})
.unwrap_or_else(|| {
panic!(
"couldn't find override number {highest_prio} in {}",
file_with_definition.display()
)
});
let line = lines_reversed
.into_iter()
.nth(line_with_current_highest)
.unwrap();
eprintln!("found, on line index {}", line);
Ok(line)
}

32
src/line.rs Normal file
View file

@ -0,0 +1,32 @@
use std::num::NonZeroU64;
#[allow(unused_imports)]
use crate::prelude::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Line(pub u64);
/// Constructors.
impl Line {
pub fn from_index(index: u64) -> Self {
Self(index)
}
pub fn from_linenr(linenr: NonZeroU64) -> Self {
Self(linenr.get() - 1)
}
}
/// Getters.
impl Line {
/// 0-indexed
pub fn index(self) -> u64 {
self.0
}
/// 1-indexed
pub fn linenr(self) -> u64 {
self.0 + 1
}
}

40
src/main.rs Normal file
View file

@ -0,0 +1,40 @@
use std::error::Error as StdError;
use std::io::IsTerminal;
use std::path::Path;
use std::process::ExitCode;
use clap::{ColorChoice, Parser as _};
fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
let args = append_override::Parser::parse();
let success = append_override::CLI_ENABLE_COLOR.set(match args.color {
ColorChoice::Always => true,
ColorChoice::Auto => std::io::stdin().is_terminal(),
ColorChoice::Never => false,
});
if cfg!(debug_assertions) {
success.expect("logic error in CLI_ENABLE_COLOR");
}
// FIXME: handle relative paths without leading ./
let filepath = Path::new(&args.file);
// Get what file that thing is defined in.
let def_path = append_override::get_where(&args.name, filepath)?;
append_override::get_highest_prio(&args.name, &def_path)?;
Ok(())
}
fn main() -> ExitCode {
match main_wrapped() {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("append-override: error: {}", e);
ExitCode::FAILURE
}
}
}

59
src/source.rs Normal file
View file

@ -0,0 +1,59 @@
use std::{
hash::{Hash, Hasher},
sync::{Arc, LazyLock},
};
use crate::Line;
#[allow(unused_imports)]
use crate::prelude::*;
use crate::color::{ANSI_CYAN, ANSI_RESET};
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct SourceLine {
pub line: Line,
pub path: Arc<Path>,
pub text: Arc<str>,
}
impl Display for SourceLine {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "line {:03}: `{ANSI_CYAN}{}{ANSI_RESET}`", self.line.linenr(), self.text.trim())
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct SourcePath {
path: Arc<Path>,
}
#[derive(Debug, Clone)]
pub struct SourceFile {
path: Arc<Path>,
file: Arc<File>,
}
impl PartialEq for SourceFile {
fn eq(&self, other: &Self) -> bool {
let paths_match = *self.path == *other.path;
if paths_match && self.file.as_raw_fd() != other.file.as_raw_fd() {
todo!("handle the case where source file paths match but FD numbers don't");
}
paths_match
}
}
impl Hash for SourceFile {
fn hash<H: Hasher>(&self, state: &mut H) {
self.path.hash(state);
self.file.as_raw_fd().hash(state);
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
enum SourceInner {
Path(SourcePath),
File(SourceFile),
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Source(SourceInner);