From e5d0bdf0c013f23c2668619d0804f6efb7c7a882 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Tue, 27 Jan 2026 17:20:04 +0100 Subject: [PATCH] skeleton continues --- src/color.rs | 12 ++++-- src/lib.rs | 107 ++++++++++++++++++-------------------------------- src/main.rs | 21 ++++++++-- src/nixcmd.rs | 27 +++++++++++++ src/source.rs | 95 ++++++++++++++++++++++++++++++++------------ 5 files changed, 161 insertions(+), 101 deletions(-) create mode 100644 src/nixcmd.rs diff --git a/src/color.rs b/src/color.rs index ea20507..19d90c7 100644 --- a/src/color.rs +++ b/src/color.rs @@ -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(|| { diff --git a/src/lib.rs b/src/lib.rs index 42f48c5..b19be90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, @@ -59,57 +59,29 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result = 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 { - 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::, IoError>>()?; - lines.reverse(); - let lines_reversed = lines; - // Get the current highest priority. let expr: OsString = [ OsStr::new("import { 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(()) } diff --git a/src/main.rs b/src/main.rs index 3889120..75d1de1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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> { 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> { // 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(()) } diff --git a/src/nixcmd.rs b/src/nixcmd.rs new file mode 100644 index 0000000..3121565 --- /dev/null +++ b/src/nixcmd.rs @@ -0,0 +1,27 @@ +#[allow(unused_imports)] +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Hash)] +pub(crate) struct NixEvalExpr { + pub(crate) expr: E, + pub(crate) attrpath: A, +} + +impl NixEvalExpr +where + E: AsRef, + A: AsRef, +{ + 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 + } +} diff --git a/src/source.rs b/src/source.rs index 0a76b75..be70116 100644 --- a/src/source.rs +++ b/src/source.rs @@ -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, - file: Arc, + file: Arc>, + lines: Arc>>, +} + +impl SourceFile { + pub fn open_from(path: Arc, options: OpenOptions) -> Result { + 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, 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::, 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 { + 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(&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);