skeleton continues

This commit is contained in:
Qyriad 2026-01-27 17:20:04 +01:00
parent bcd11513ef
commit e5d0bdf0c0
5 changed files with 161 additions and 101 deletions

View file

@ -34,10 +34,16 @@ impl Display for _LazyLockDisplay {
} }
} }
pub(crate) const ANSI_GREEN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
SHOULD_COLOR.then_some("\x1b[32m").unwrap_or_default()
}));
pub(crate) const ANSI_MAGENTA: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
SHOULD_COLOR.then_some("\x1b[35m").unwrap_or_default()
}));
pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
SHOULD_COLOR SHOULD_COLOR.then_some("\x1b[36m").unwrap_or_default()
.then_some("\x1b[36m")
.unwrap_or_default()
})); }));
pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {

View file

@ -1,3 +1,5 @@
use std::{io::BufWriter, sync::Arc};
pub(crate) mod prelude { pub(crate) mod prelude {
#![allow(unused_imports)] #![allow(unused_imports)]
@ -27,22 +29,20 @@ pub(crate) mod prelude {
use prelude::*; use prelude::*;
use std::{
io::{BufRead, BufReader},
sync::Arc,
};
pub mod args; pub mod args;
pub use args::Parser; pub use args::Parser;
mod color; mod color;
pub use color::{CLI_ENABLE_COLOR, SHOULD_COLOR}; pub use color::{CLI_ENABLE_COLOR, SHOULD_COLOR};
pub mod line; pub mod line;
mod nixcmd;
pub use line::Line; pub use line::Line;
pub mod source; pub mod source;
pub use source::SourceLine; pub use source::SourceLine;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::source::SourceFile;
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub struct DefinitionWithLocation { pub struct DefinitionWithLocation {
pub file: Box<Path>, pub file: Box<Path>,
@ -59,57 +59,29 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path
.into_iter() .into_iter()
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.collect(); .collect();
let result = Command::new("nix-instantiate")
.arg("--eval") let attrpath = format!("options.{}.definitionsWithLocations", option_name);
.arg("--json")
.arg("--strict") let output = nixcmd::NixEvalExpr { expr, attrpath }
.arg("--expr") .into_command()
.arg(expr)
.arg("-A")
.arg(format!("options.{}.definitionsWithLocations", option_name))
.output_checked_utf8()?; .output_checked_utf8()?;
let stdout = result.stdout(); let stdout = output.stdout();
let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?; let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?;
let last_location = definitions.last().unwrap(); let last_location = definitions.into_iter().last().unwrap();
let file = &*last_location.file;
dbg!(&file);
Ok(Box::from(file)) Ok(Box::from(last_location.file))
} }
pub fn get_highest_prio( pub fn get_highest_prio(
option_name: &str, option_name: &str,
file_with_definition: &Path, mut source: SourceFile,
) -> Result<SourceLine, BoxDynError> { ) -> 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. // Get the current highest priority.
let expr: OsString = [ let expr: OsString = [
OsStr::new("import <nixpkgs/nixos> { configuration = "), OsStr::new("import <nixpkgs/nixos> { configuration = "),
file_with_definition.as_os_str(), source.path().as_os_str(),
OsStr::new("; }"), OsStr::new("; }"),
] ]
.into_iter() .into_iter()
@ -117,41 +89,40 @@ pub fn get_highest_prio(
.collect(); .collect();
// Get the highest priority, and the file its defined in. // Get the highest priority, and the file its defined in.
let result = Command::new("nix-instantiate") let attrpath = format!("options.{}.highestPrio", option_name);
.arg("--eval") let output = nixcmd::NixEvalExpr { expr, attrpath }
.arg("--json") .into_command()
.arg("--strict")
.arg("--expr")
.arg(expr)
.arg("-A")
.arg(format!("options.{}.highestPrio", option_name))
.output_checked_utf8()?; .output_checked_utf8()?;
let stdout = result.stdout(); let stdout = output.stdout();
let highest_prio = stdout.trim(); let highest_prio = stdout.trim();
let needle = format!("lib.mkOverride ({})", highest_prio); let needle = format!("lib.mkOverride ({})", highest_prio);
eprintln!("looking for {needle:?}");
let line_with_current_highest = lines_reversed let path = source.path();
let lines = source.lines()?;
let line = lines
.iter() .iter()
.position(|line| { // We're more likely to find it at the end, so let's start there.
eprintln!("looking for {needle} in {line}"); .rev()
.find(|&line| line.text.contains(&needle))
line.text.contains(&needle)
})
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"couldn't find override number {highest_prio} in {}", "couldn't find override number {highest_prio} in {}",
file_with_definition.display() path.display(),
) )
}); });
let line = lines_reversed Ok(line.clone())
.into_iter() }
.nth(line_with_current_highest)
.unwrap(); pub fn write_next_prio(
mut source: SourceFile,
eprintln!("found, on line index {}", line); last_line_def: SourceLine,
new_prio: u64,
Ok(line) ) -> Result<(), BoxDynError> {
let lines = source.lines()?;
for line in lines {}
Ok(())
} }

View file

@ -1,17 +1,20 @@
use std::error::Error as StdError; use std::{error::Error as StdError, sync::Arc};
use std::io::IsTerminal; use std::io::{self, IsTerminal};
use std::path::Path; use std::path::Path;
use std::process::ExitCode; use std::process::ExitCode;
use append_override::source::SourceFile;
use clap::{ColorChoice, Parser as _}; use clap::{ColorChoice, Parser as _};
use fs_err::File;
use fs_err::os::unix::fs::OpenOptionsExt;
fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> { fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
let args = append_override::Parser::parse(); let args = append_override::Parser::parse();
let success = append_override::CLI_ENABLE_COLOR.set(match args.color { let success = append_override::CLI_ENABLE_COLOR.set(match args.color {
ColorChoice::Always => true, ColorChoice::Always => true,
ColorChoice::Auto => std::io::stdin().is_terminal(), ColorChoice::Auto => io::stdin().is_terminal(),
ColorChoice::Never => false, ColorChoice::Never => false,
}); });
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
@ -23,8 +26,18 @@ fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
// Get what file that thing is defined in. // Get what file that thing is defined in.
let def_path = append_override::get_where(&args.name, filepath)?; let def_path = append_override::get_where(&args.name, filepath)?;
let def_path = Arc::from(def_path);
let mut opts = File::options();
opts
.read(true)
.write(true)
.create(false)
.custom_flags(libc::O_CLOEXEC);
let source_file = SourceFile::open_from(Arc::clone(&def_path), opts)?;
append_override::get_highest_prio(&args.name, &def_path)?; let last_def_line = append_override::get_highest_prio(&args.name, source_file)?;
eprintln!("{last_def_line}");
Ok(()) Ok(())
} }

27
src/nixcmd.rs Normal file
View file

@ -0,0 +1,27 @@
#[allow(unused_imports)]
use crate::prelude::*;
#[derive(Debug, Clone, PartialEq, Hash)]
pub(crate) struct NixEvalExpr<E, A> {
pub(crate) expr: E,
pub(crate) attrpath: A,
}
impl<E, A> NixEvalExpr<E, A>
where
E: AsRef<OsStr>,
A: AsRef<OsStr>,
{
pub(crate) fn into_command(self) -> Command {
let mut cmd = Command::new("nix-instantiate");
cmd.arg("--eval")
.arg("--json")
.arg("--strict")
.arg("--expr")
.arg(self.expr)
.arg("-A")
.arg(self.attrpath);
cmd
}
}

View file

@ -1,12 +1,16 @@
use std::{ use std::{
hash::{Hash, Hasher}, hash::Hash,
sync::{Arc, LazyLock}, io::{BufRead, BufReader},
ops::{Deref, DerefMut},
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError},
}; };
use crate::Line; use crate::Line;
use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET};
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::prelude::*; use crate::prelude::*;
use crate::color::{ANSI_CYAN, ANSI_RESET};
use fs_err::OpenOptions;
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct SourceLine { pub struct SourceLine {
@ -17,7 +21,13 @@ pub struct SourceLine {
impl Display for SourceLine { impl Display for SourceLine {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "line {:03}: `{ANSI_CYAN}{}{ANSI_RESET}`", self.line.linenr(), self.text.trim()) write!(
f,
"{ANSI_MAGENTA}{}{ANSI_RESET}:{ANSI_GREEN}{}{ANSI_RESET}: `{ANSI_CYAN}{}{ANSI_RESET}`",
self.path.display(),
self.line.linenr(),
self.text.trim(),
)
} }
} }
@ -29,31 +39,64 @@ pub struct SourcePath {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SourceFile { pub struct SourceFile {
path: Arc<Path>, path: Arc<Path>,
file: Arc<File>, file: Arc<Mutex<File>>,
lines: Arc<OnceLock<Vec<SourceLine>>>,
}
impl SourceFile {
pub fn open_from(path: Arc<Path>, options: OpenOptions) -> Result<Self, IoError> {
let file = Arc::new(Mutex::new(options.open(&*path)?));
Ok(Self {
path,
file,
lines: Arc::new(OnceLock::new()),
})
}
pub fn buf_reader(&mut self) -> Result<BufReader<&mut File>, IoError> {
let file_mut = Arc::get_mut(&mut self.file)
.unwrap_or_else(|| panic!("'File' for {} has existing handle", self.path.display()))
.get_mut()
.unwrap_or_else(|e| {
panic!("'File' for {} was mutex-poisoned: {e}", self.path.display())
});
let reader = BufReader::new(file_mut);
Ok(reader)
}
pub fn lines(&self) -> Result<&[SourceLine], IoError> {
if let Some(lines) = self.lines.get() {
return Ok(lines);
}
let lines = BufReader::new(&*self.file.lock().unwrap())
.lines()
.enumerate()
.map(|(index, line_res)| {
line_res.map(|line| SourceLine {
line: Line::from_index(index as u64),
path: Arc::clone(&self.path),
text: Arc::from(line),
})
})
.collect::<Result<Vec<SourceLine>, IoError>>()?;
// Mutex should have dropped by now.
debug_assert!(self.file.try_lock().is_ok());
self.lines.set(lines).unwrap();
Ok(self.lines.get().unwrap().as_slice())
}
pub fn path(&self) -> Arc<Path> {
Arc::clone(&self.path)
}
} }
impl PartialEq for SourceFile { impl PartialEq for SourceFile {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
let paths_match = *self.path == *other.path; *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);