diff --git a/src/lib.rs b/src/lib.rs index 7a7f784..d939afa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; pub(crate) mod prelude { #![allow(unused_imports)] @@ -43,6 +43,8 @@ use serde::{Deserialize, Serialize}; use crate::source::SourceFile; +pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' ']; + #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] pub struct DefinitionWithLocation { pub file: Box, @@ -75,8 +77,8 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result Result { + source: SourceFile, +) -> Result<(i64, SourceLine), BoxDynError> { // Get the current highest priority. let expr: OsString = [ @@ -93,7 +95,7 @@ pub fn get_highest_prio( .into_command() .output_checked_utf8()?; let stdout = output.stdout(); - let highest_prio = stdout.trim(); + let highest_prio = i64::from_str(stdout.trim())?; let needle = format!("lib.mkOverride ({})", highest_prio); @@ -111,27 +113,77 @@ pub fn get_highest_prio( ) }); - Ok(line.clone()) + Ok((highest_prio, line.clone())) +} + +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 new_line = SourceLine { + line: next_line.line.next(), + path: source.path(), + text: Arc::from(new_text), + }; + + Ok(new_line) } pub fn write_next_prio( mut source: SourceFile, last_line_def: SourceLine, - last_pri: i64, - new_prio: i64, + new_text: Arc, ) -> Result<(), BoxDynError> { //let lines = source.lines()?; - let old_text = last_line_def.text(); - let new_text = old_text.replace(last_pri.to_string().as_str(), new_prio.to_string().as_str()); + let open_brace_line = source.line(last_line_def.line.prev())?.text(); + let close_brace_line = source.line(last_line_def.line.next())?.text(); + let new_mod_start = SourceLine { + line: last_line_def.line.next(), + path: source.path(), + text: open_brace_line, + }; let new_line = SourceLine { - line: Line::from_index(last_line_def.line.index() + 1), + line: new_mod_start.line.next(), path: source.path(), text: Arc::from(new_text), }; + let new_mod_end = SourceLine { + line: new_line.line.next(), + path: source.path(), + text: close_brace_line, + }; - source.insert_line(new_line.line, new_line.text())?; + 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())?; Ok(()) } diff --git a/src/line.rs b/src/line.rs index 7961b95..e7ec169 100644 --- a/src/line.rs +++ b/src/line.rs @@ -9,24 +9,35 @@ pub struct Line(pub u64); /// Constructors. impl Line { - pub fn from_index(index: u64) -> Self { + pub const fn from_index(index: u64) -> Self { Self(index) } - pub fn from_linenr(linenr: NonZeroU64) -> Self { + pub const fn from_linenr(linenr: NonZeroU64) -> Self { Self(linenr.get() - 1) } + + pub const fn next(self) -> Self { + Self::from_index(self.index() + 1) + } + + /// Panics if self is line index 0. + pub const fn prev(self) -> Self { + Self::from_index(self.index() - 1) + } } /// Getters. impl Line { /// 0-indexed - pub fn index(self) -> u64 { + pub const fn index(self) -> u64 { self.0 } /// 1-indexed - pub fn linenr(self) -> u64 { + pub const fn linenr(self) -> u64 { self.0 + 1 } } + +pub struct Lines(Vec); diff --git a/src/main.rs b/src/main.rs index e93d768..f9bfa8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ -use std::{error::Error as StdError, sync::Arc}; use std::io::{self, IsTerminal}; use std::path::Path; use std::process::ExitCode; - +use std::{error::Error as StdError, sync::Arc}; use append_override::source::SourceFile; use clap::{ColorChoice, Parser as _}; @@ -28,31 +27,28 @@ fn main_wrapped() -> Result<(), Box> { 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) + opts.read(true) .write(true) .create(false) .custom_flags(libc::O_CLOEXEC); let source_file = SourceFile::open_from(Arc::clone(&def_path), opts)?; - let last_def_line = append_override::get_highest_prio(&args.name, source_file.clone())?; - let pri: i64 = { - let text = last_def_line.text(); - let pat = "lib.mkOverride ("; - let start = text.find(pat).unwrap(); - let substr = &text[start..]; - let substr = substr.strip_prefix(pat).unwrap(); - let end_paren = substr.find(")").unwrap(); - let final_num = &substr[..end_paren]; - - final_num.parse().unwrap() - }; + let (pri, last_def_line) = append_override::get_highest_prio(&args.name, source_file.clone())?; + let new_pri = pri - 1; eprintln!("{last_def_line}"); - dbg!(&pri); + 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(), + )?; - append_override::write_next_prio(source_file, last_def_line, pri, pri - 1)?; + eprintln!("new_pri_line={new_pri_line}"); + + append_override::write_next_prio(source_file, last_def_line, new_pri_line.text())?; Ok(()) } diff --git a/src/source.rs b/src/source.rs index f5aa785..7a482db 100644 --- a/src/source.rs +++ b/src/source.rs @@ -48,6 +48,8 @@ pub struct SourcePath { pub struct SourceFile { path: Arc, file: Arc>, + /// References to `SourceFile` do not prevent mutating `lines`. + /// Also `lines` is lazily initialized. lines: Arc>>>, } @@ -119,6 +121,75 @@ impl SourceFile { Ref::map(self.lines.get().unwrap().borrow(), |lines| lines.as_slice()) } + /// With debug assertions, panics if `lines` are not contiguous. + pub fn insert_lines(&mut self, new_lines: &[SourceLine]) -> Result<(), IoError> { + if new_lines.is_empty() { + return Ok(()); + } + + debug_assert!(new_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line)); + + let path = self.path(); + let cur_lines = self.lines()?; + let first_half = cur_lines + .iter() + .take(new_lines.last().unwrap().line.prev().index() as usize) + .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) + .map(SourceLine::text); + + let final_lines: Vec = first_half + .chain(middle) + .chain(second_half) + .enumerate() + .map(|(idx, text)| SourceLine { + line: Line::from_index(idx as u64), + text, + path: self.path(), + }) + .collect(); + + // Assert lines are continuous. + debug_assert!(final_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line)); + debug_assert_eq!(cur_lines.len() + new_lines.len(), final_lines.len()); + + 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)?; + + // Finally, update state. + self.lines.get().unwrap().replace(final_lines); + + Ok(()) + } + pub fn insert_line(&mut self, at: Line, text: Arc) -> Result<(), IoError> { self.lines()?; let path = self.path();