diff --git a/Cargo.lock b/Cargo.lock index d5fd1ed..53b5645 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,11 @@ dependencies = [ "clap", "command-error", "fs-err", + "itertools", "libc", "serde", "serde_json", + "tap", ] [[package]] @@ -152,6 +154,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -195,6 +203,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -339,6 +356,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tracing" version = "0.1.44" diff --git a/Cargo.toml b/Cargo.toml index d0ea161..206f072 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ path = "src/lib.rs" clap = { version = "4.5.54", features = ["color", "derive"] } command-error = "0.8.0" fs-err = "3.2.2" +itertools = "0.14.0" libc = { version = "0.2.180", features = ["extra_traits"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" +tap = "1.0.1" diff --git a/src/lib.rs b/src/lib.rs index d939afa..ce762d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, LazyLock}; +use std::sync::Arc; pub(crate) mod prelude { #![allow(unused_imports)] @@ -25,6 +25,8 @@ pub(crate) mod prelude { pub use fs_err::File; #[cfg(unix)] pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt}; + + pub use tap::{Pipe, Tap}; } use prelude::*; @@ -48,20 +50,21 @@ pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' ']; #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] pub struct DefinitionWithLocation { pub file: Box, - pub value: Box, + pub value: Box, } -pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result, BoxDynError> { - let expr: OsString = [ - // foo +pub fn expr_for_configuration(source_file: &Path) -> OsString { + [ OsStr::new("import { configuration = "), - configuration_nix.as_os_str(), + source_file.as_os_str(), OsStr::new("; }"), ] .into_iter() - .map(ToOwned::to_owned) - .collect(); + .collect() +} +pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result, BoxDynError> { + let expr = expr_for_configuration(configuration_nix); let attrpath = format!("options.{}.definitionsWithLocations", option_name); let output = nixcmd::NixEvalExpr { expr, attrpath } @@ -75,19 +78,10 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result Result<(i64, SourceLine), BoxDynError> { +pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result { // Get the current highest priority. - let expr: OsString = [ - OsStr::new("import { configuration = "), - source.path().as_os_str(), - OsStr::new("; }"), - ] - .into_iter() - .collect(); + let expr = expr_for_configuration(&source.path()); // Get the highest priority, and the file its defined in. let attrpath = format!("options.{}.highestPrio", option_name); @@ -97,93 +91,44 @@ pub fn get_highest_prio( let stdout = output.stdout(); let highest_prio = i64::from_str(stdout.trim())?; - let needle = format!("lib.mkOverride ({})", highest_prio); - - let path = source.path(); - let lines = source.lines()?; - let line = lines - .iter() - // 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 {}", - path.display(), - ) - }); - - Ok((highest_prio, line.clone())) + Ok(highest_prio) } pub fn get_next_prio_line( source: SourceFile, option_name: Arc, - last_line_def: SourceLine, new_prio: i64, new_value: Arc, ) -> Result { - if !last_line_def.text.ends_with(';') { - todo!(); - } - let next_line = source.line(last_line_def.line.next())?; - if next_line.text.trim() != "}" { - todo!(); - } - - let (indentation, _rest) = last_line_def.text.split_at( - last_line_def - .text - .find(|ch: char| !ch.is_ascii_whitespace()) - .unwrap_or_default(), - ); - // FIXME: fix indentation - let new_text = format!("{indentation}{option_name} = lib.mkOverride ({new_prio}) ({new_value});",); + 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 new_line = SourceLine { - line: next_line.line.next(), + line: last_line.line, path: source.path(), - text: Arc::from(new_text), + text: Arc::from(format!( + " {option_name} = lib.mkOverride ({new_prio}) ({new_value});", + )), }; Ok(new_line) } -pub fn write_next_prio( - mut source: SourceFile, - last_line_def: SourceLine, - new_text: Arc, -) -> Result<(), BoxDynError> { - //let lines = source.lines()?; - - let open_brace_line = source.line(last_line_def.line.prev())?.text(); - let close_brace_line = source.line(last_line_def.line.next())?.text(); - +pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(), BoxDynError> { let new_mod_start = SourceLine { - line: last_line_def.line.next(), + line: new_line.line.prev(), path: source.path(), - text: open_brace_line, - }; - let new_line = SourceLine { - line: new_mod_start.line.next(), - path: source.path(), - text: Arc::from(new_text), + text: Arc::from(" {"), }; let new_mod_end = SourceLine { line: new_line.line.next(), path: source.path(), - text: close_brace_line, + text: Arc::from(" }"), }; - dbg!(&new_mod_start.text()); - - source.insert_lines(&[ - new_mod_start, - new_line, - new_mod_end, - ])?; - - //source.insert_line(new_line.line, new_line.text())?; + source.insert_lines(&[new_mod_start, new_line, new_mod_end])?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index faf153a..60a4654 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,8 @@ use fs_err::os::unix::fs::OpenOptionsExt; fn main_wrapped() -> Result<(), Box> { let args = append_override::Parser::parse(); + dbg!(&args); + let success = append_override::CLI_ENABLE_COLOR.set(match args.color { ColorChoice::Always => true, ColorChoice::Auto => io::stdin().is_terminal(), @@ -33,6 +35,7 @@ fn main_wrapped() -> Result<(), Box> { // Get what file that thing is defined in. let def_path = append_override::get_where(&args.name, &filepath)?; + dbg!(&def_path); let def_path = Arc::from(def_path); let mut opts = File::options(); opts.read(true) @@ -41,22 +44,19 @@ fn main_wrapped() -> Result<(), Box> { .custom_flags(libc::O_CLOEXEC); let source_file = SourceFile::open_from(Arc::clone(&def_path), opts)?; - let (pri, last_def_line) = append_override::get_highest_prio(&args.name, source_file.clone())?; + let pri = append_override::get_highest_prio(&args.name, source_file.clone())?; let new_pri = pri - 1; - eprintln!("{last_def_line}"); - let new_pri_line = append_override::get_next_prio_line( source_file.clone(), args.name.into(), - last_def_line.clone(), new_pri, args.value.into(), )?; eprintln!("new_pri_line={new_pri_line}"); - append_override::write_next_prio(source_file, last_def_line, new_pri_line.text())?; + append_override::write_next_prio(source_file, new_pri_line)?; Ok(()) } diff --git a/src/source.rs b/src/source.rs index 7a482db..5a6185d 100644 --- a/src/source.rs +++ b/src/source.rs @@ -1,10 +1,10 @@ use std::{ - cell::{Cell, Ref, RefCell}, + cell::{Ref, RefCell}, hash::Hash, io::{BufRead, BufReader, BufWriter}, - iter, mem, - ops::{Deref, DerefMut}, - sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError, RwLock}, + ops::Deref, + ptr, + sync::{Arc, Mutex, OnceLock}, }; use crate::Line; @@ -13,6 +13,33 @@ use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET}; use crate::prelude::*; use fs_err::OpenOptions; +use itertools::Itertools; + +pub fn replace_file<'a>( + path: &Path, + contents: impl IntoIterator, +) -> Result<(), IoError> { + let tmp_path = path.with_added_extension(".tmp"); + let tmp_file = File::options() + .create(true) + .write(true) + .truncate(true) + .custom_flags(libc::O_EXCL | libc::O_CLOEXEC) + .open(&tmp_path)?; + + let mut writer = BufWriter::new(tmp_file); + for slice in contents { + writer.write_all(slice)?; + } + + writer.flush()?; + drop(writer); + + // Rename the temporary file to the new file, which is atomic (TODO: I think). + fs_err::rename(&tmp_path, &path)?; + + Ok(()) +} #[derive(Debug, Clone, PartialEq, Hash)] pub struct SourceLine { @@ -25,6 +52,30 @@ impl SourceLine { pub fn text(&self) -> Arc { Arc::clone(&self.text) } + + pub fn text_ref(&self) -> &str { + &self.text + } + + pub fn text_bytes(&self) -> Arc<[u8]> { + let len: usize = self.text.as_bytes().len(); + + // We need to consume an Arc, but we are &self. + let text = Arc::clone(&self.text); + let str_ptr: *const str = Arc::into_raw(text); + let start: *const u8 = str_ptr.cast(); + let slice_ptr: *const [u8] = ptr::slice_from_raw_parts(start, len); + + unsafe { Arc::<[u8]>::from_raw(slice_ptr) } + } + + pub fn text_bytes_ref(&self) -> &[u8] { + self.text.as_bytes() + } + + pub fn path(&self) -> Arc { + Arc::clone(&self.path) + } } impl Display for SourceLine { @@ -39,11 +90,6 @@ impl Display for SourceLine { } } -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct SourcePath { - path: Arc, -} - #[derive(Debug, Clone)] pub struct SourceFile { path: Arc, @@ -126,6 +172,7 @@ impl SourceFile { if new_lines.is_empty() { return Ok(()); } + let num_lines_before_new = new_lines.last().unwrap().line.prev().index() as usize; debug_assert!(new_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line)); @@ -133,12 +180,12 @@ impl SourceFile { let cur_lines = self.lines()?; let first_half = cur_lines .iter() - .take(new_lines.last().unwrap().line.prev().index() as usize) + .take(num_lines_before_new) .map(SourceLine::text); let middle = new_lines.iter().map(SourceLine::text); let second_half = cur_lines .iter() - .skip(new_lines.last().unwrap().line.prev().index() as usize) + .skip(num_lines_before_new) .map(SourceLine::text); let final_lines: Vec = first_half @@ -158,31 +205,11 @@ impl SourceFile { drop(cur_lines); - // Write it to a file in the same directory. - let new_name: OsString = [ - // foo - path.file_name().unwrap(), - OsStr::new(".tmp"), - ] - .into_iter() - .collect::(); - let tmp_path = path.with_file_name(&new_name); - let tmp_file = File::options() - .create(true) - .write(true) - .truncate(true) - .custom_flags(libc::O_EXCL | libc::O_CLOEXEC) - .open(&tmp_path)?; - - let mut writer = BufWriter::new(tmp_file); - for line in final_lines.iter() { - writer.write_all(line.text().as_bytes())?; - writer.write_all(b"\n")?; - } - writer.flush()?; - drop(writer); - // Rename the temporary file to the new file, which is atomic (TODO: I think). - fs_err::rename(&tmp_path, &path)?; + let data = final_lines + .iter() + .map(SourceLine::text_bytes_ref) + .pipe(|iterator| Itertools::intersperse(iterator, b"\n")); + replace_file(&path, data)?; // Finally, update state. self.lines.get().unwrap().replace(final_lines); @@ -190,74 +217,6 @@ impl SourceFile { Ok(()) } - pub fn insert_line(&mut self, at: Line, text: Arc) -> Result<(), IoError> { - self.lines()?; - let path = self.path(); - - //let lines = Arc::get_mut(&mut self.lines).unwrap().get_mut().unwrap(); - let lines_guard = self.lines.get().unwrap().borrow(); - let lines = &*lines_guard; - let first_half = lines.iter().take(at.index() as usize).map(SourceLine::text); - let second_half = lines.iter().skip(at.index() as usize).map(SourceLine::text); - - let new_lines: Vec = first_half - .chain(iter::once(Arc::clone(&text))) - .chain(second_half) - .enumerate() - .map(|(idx, text)| SourceLine { - line: Line::from_index(idx as u64), - text, - path: Arc::clone(&path), - }) - .collect(); - - if cfg!(debug_assertions) { - assert_eq!(new_lines.len(), lines.len() + 1); - let newly = new_lines.get(at.index() as usize); - assert_eq!(newly.map(SourceLine::text), Some(text)); - - // Assert lines are continuous. - let linenrs: Vec = new_lines - .iter() - .map(|source_line| source_line.line) - .collect(); - assert!(linenrs.is_sorted()); - } - - // Write it to a file in the same directory. - let new_name: OsString = [ - // foo - path.file_name().unwrap(), - OsStr::new(".tmp"), - ] - .into_iter() - .collect::(); - let tmp_path = path.with_file_name(&new_name); - let tmp_file = File::options() - .create(true) - .write(true) - .truncate(true) - .custom_flags(libc::O_EXCL | libc::O_CLOEXEC) - .open(&tmp_path)?; - - let mut writer = BufWriter::new(tmp_file); - for line in new_lines.iter() { - writer.write_all(line.text().as_bytes())?; - writer.write_all(b"\n")?; - } - writer.flush()?; - drop(writer); - // Rename the temporary file to the new file, which is atomic (TODO: I think). - fs_err::rename(&tmp_path, &path)?; - - drop(lines_guard); - let mut lines_guard = self.lines.get().unwrap().borrow_mut(); - // Finally, update state. - let _old_lines = mem::replace(&mut *lines_guard, new_lines); - - Ok(()) - } - pub fn path(&self) -> Arc { Arc::clone(&self.path) }