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(|| {
SHOULD_COLOR
.then_some("\x1b[36m")
.unwrap_or_default()
SHOULD_COLOR.then_some("\x1b[36m").unwrap_or_default()
}));
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 {
#![allow(unused_imports)]
@ -27,22 +29,20 @@ pub(crate) mod prelude {
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;
mod nixcmd;
pub use line::Line;
pub mod source;
pub use source::SourceLine;
use serde::{Deserialize, Serialize};
use crate::source::SourceFile;
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub struct DefinitionWithLocation {
pub file: Box<Path>,
@ -59,57 +59,29 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path
.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))
let attrpath = format!("options.{}.definitionsWithLocations", option_name);
let output = nixcmd::NixEvalExpr { expr, attrpath }
.into_command()
.output_checked_utf8()?;
let stdout = result.stdout();
let stdout = output.stdout();
let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?;
let last_location = definitions.last().unwrap();
let file = &*last_location.file;
dbg!(&file);
let last_location = definitions.into_iter().last().unwrap();
Ok(Box::from(file))
Ok(Box::from(last_location.file))
}
pub fn get_highest_prio(
option_name: &str,
file_with_definition: &Path,
mut source: SourceFile,
) -> 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(),
source.path().as_os_str(),
OsStr::new("; }"),
]
.into_iter()
@ -117,41 +89,40 @@ pub fn get_highest_prio(
.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))
let attrpath = format!("options.{}.highestPrio", option_name);
let output = nixcmd::NixEvalExpr { expr, attrpath }
.into_command()
.output_checked_utf8()?;
let stdout = result.stdout();
let stdout = output.stdout();
let highest_prio = stdout.trim();
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()
.position(|line| {
eprintln!("looking for {needle} in {line}");
line.text.contains(&needle)
})
// We're more likely to find it at the end, so let's start there.
.rev()
.find(|&line| line.text.contains(&needle))
.unwrap_or_else(|| {
panic!(
"couldn't find override number {highest_prio} in {}",
file_with_definition.display()
path.display(),
)
});
let line = lines_reversed
.into_iter()
.nth(line_with_current_highest)
.unwrap();
eprintln!("found, on line index {}", line);
Ok(line)
Ok(line.clone())
}
pub fn write_next_prio(
mut source: SourceFile,
last_line_def: SourceLine,
new_prio: u64,
) -> 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::io::IsTerminal;
use std::{error::Error as StdError, sync::Arc};
use std::io::{self, IsTerminal};
use std::path::Path;
use std::process::ExitCode;
use append_override::source::SourceFile;
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>> {
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::Auto => io::stdin().is_terminal(),
ColorChoice::Never => false,
});
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.
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(())
}

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::{
hash::{Hash, Hasher},
sync::{Arc, LazyLock},
hash::Hash,
io::{BufRead, BufReader},
ops::{Deref, DerefMut},
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError},
};
use crate::Line;
use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET};
#[allow(unused_imports)]
use crate::prelude::*;
use crate::color::{ANSI_CYAN, ANSI_RESET};
use fs_err::OpenOptions;
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct SourceLine {
@ -17,7 +21,13 @@ pub struct SourceLine {
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())
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)]
pub struct SourceFile {
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 {
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
*self.path == *other.path
}
}
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);