don't require existing override
This commit is contained in:
parent
7bce1e7a6e
commit
80ff0b36cb
5 changed files with 120 additions and 191 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
|
@ -59,9 +59,11 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"command-error",
|
"command-error",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
|
"itertools",
|
||||||
"libc",
|
"libc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -152,6 +154,12 @@ version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -195,6 +203,15 @@ version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
|
|
@ -339,6 +356,12 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tap"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ path = "src/lib.rs"
|
||||||
clap = { version = "4.5.54", features = ["color", "derive"] }
|
clap = { version = "4.5.54", features = ["color", "derive"] }
|
||||||
command-error = "0.8.0"
|
command-error = "0.8.0"
|
||||||
fs-err = "3.2.2"
|
fs-err = "3.2.2"
|
||||||
|
itertools = "0.14.0"
|
||||||
libc = { version = "0.2.180", features = ["extra_traits"] }
|
libc = { version = "0.2.180", features = ["extra_traits"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
tap = "1.0.1"
|
||||||
|
|
|
||||||
109
src/lib.rs
109
src/lib.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use std::sync::{Arc, LazyLock};
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub(crate) mod prelude {
|
pub(crate) mod prelude {
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
@ -25,6 +25,8 @@ pub(crate) mod prelude {
|
||||||
pub use fs_err::File;
|
pub use fs_err::File;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt};
|
pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt};
|
||||||
|
|
||||||
|
pub use tap::{Pipe, Tap};
|
||||||
}
|
}
|
||||||
|
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
@ -48,20 +50,21 @@ pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' '];
|
||||||
#[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>,
|
||||||
pub value: Box<str>,
|
pub value: Box<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path>, BoxDynError> {
|
pub fn expr_for_configuration(source_file: &Path) -> OsString {
|
||||||
let expr: OsString = [
|
[
|
||||||
// foo
|
|
||||||
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
||||||
configuration_nix.as_os_str(),
|
source_file.as_os_str(),
|
||||||
OsStr::new("; }"),
|
OsStr::new("; }"),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(ToOwned::to_owned)
|
.collect()
|
||||||
.collect();
|
}
|
||||||
|
|
||||||
|
pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path>, BoxDynError> {
|
||||||
|
let expr = expr_for_configuration(configuration_nix);
|
||||||
let attrpath = format!("options.{}.definitionsWithLocations", option_name);
|
let attrpath = format!("options.{}.definitionsWithLocations", option_name);
|
||||||
|
|
||||||
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||||
|
|
@ -75,19 +78,10 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path
|
||||||
Ok(Box::from(last_location.file))
|
Ok(Box::from(last_location.file))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_highest_prio(
|
pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result<i64, BoxDynError> {
|
||||||
option_name: &str,
|
|
||||||
source: SourceFile,
|
|
||||||
) -> Result<(i64, SourceLine), BoxDynError> {
|
|
||||||
// Get the current highest priority.
|
// Get the current highest priority.
|
||||||
|
|
||||||
let expr: OsString = [
|
let expr = expr_for_configuration(&source.path());
|
||||||
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
|
||||||
source.path().as_os_str(),
|
|
||||||
OsStr::new("; }"),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Get the highest priority, and the file its defined in.
|
// Get the highest priority, and the file its defined in.
|
||||||
let attrpath = format!("options.{}.highestPrio", option_name);
|
let attrpath = format!("options.{}.highestPrio", option_name);
|
||||||
|
|
@ -97,93 +91,44 @@ pub fn get_highest_prio(
|
||||||
let stdout = output.stdout();
|
let stdout = output.stdout();
|
||||||
let highest_prio = i64::from_str(stdout.trim())?;
|
let highest_prio = i64::from_str(stdout.trim())?;
|
||||||
|
|
||||||
let needle = format!("lib.mkOverride ({})", highest_prio);
|
Ok(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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_next_prio_line(
|
pub fn get_next_prio_line(
|
||||||
source: SourceFile,
|
source: SourceFile,
|
||||||
option_name: Arc<str>,
|
option_name: Arc<str>,
|
||||||
last_line_def: SourceLine,
|
|
||||||
new_prio: i64,
|
new_prio: i64,
|
||||||
new_value: Arc<str>,
|
new_value: Arc<str>,
|
||||||
) -> Result<SourceLine, BoxDynError> {
|
) -> Result<SourceLine, BoxDynError> {
|
||||||
if !last_line_def.text.ends_with(';') {
|
let source_lines = source.lines()?;
|
||||||
todo!();
|
let last_line = source_lines.last();
|
||||||
}
|
assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]"));
|
||||||
let next_line = source.line(last_line_def.line.next())?;
|
let last_line = last_line.unwrap();
|
||||||
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 {
|
let new_line = SourceLine {
|
||||||
line: next_line.line.next(),
|
line: last_line.line,
|
||||||
path: source.path(),
|
path: source.path(),
|
||||||
text: Arc::from(new_text),
|
text: Arc::from(format!(
|
||||||
|
" {option_name} = lib.mkOverride ({new_prio}) ({new_value});",
|
||||||
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(new_line)
|
Ok(new_line)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_next_prio(
|
pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(), BoxDynError> {
|
||||||
mut source: SourceFile,
|
|
||||||
last_line_def: SourceLine,
|
|
||||||
new_text: Arc<str>,
|
|
||||||
) -> 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();
|
|
||||||
|
|
||||||
let new_mod_start = SourceLine {
|
let new_mod_start = SourceLine {
|
||||||
line: last_line_def.line.next(),
|
line: new_line.line.prev(),
|
||||||
path: source.path(),
|
path: source.path(),
|
||||||
text: open_brace_line,
|
text: Arc::from(" {"),
|
||||||
};
|
|
||||||
let new_line = SourceLine {
|
|
||||||
line: new_mod_start.line.next(),
|
|
||||||
path: source.path(),
|
|
||||||
text: Arc::from(new_text),
|
|
||||||
};
|
};
|
||||||
let new_mod_end = SourceLine {
|
let new_mod_end = SourceLine {
|
||||||
line: new_line.line.next(),
|
line: new_line.line.next(),
|
||||||
path: source.path(),
|
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_lines(&[
|
|
||||||
new_mod_start,
|
|
||||||
new_line,
|
|
||||||
new_mod_end,
|
|
||||||
])?;
|
|
||||||
|
|
||||||
//source.insert_line(new_line.line, new_line.text())?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/main.rs
10
src/main.rs
|
|
@ -13,6 +13,8 @@ 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();
|
||||||
|
|
||||||
|
dbg!(&args);
|
||||||
|
|
||||||
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 => io::stdin().is_terminal(),
|
ColorChoice::Auto => io::stdin().is_terminal(),
|
||||||
|
|
@ -33,6 +35,7 @@ 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)?;
|
||||||
|
dbg!(&def_path);
|
||||||
let def_path = Arc::from(def_path);
|
let def_path = Arc::from(def_path);
|
||||||
let mut opts = File::options();
|
let mut opts = File::options();
|
||||||
opts.read(true)
|
opts.read(true)
|
||||||
|
|
@ -41,22 +44,19 @@ fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
|
||||||
.custom_flags(libc::O_CLOEXEC);
|
.custom_flags(libc::O_CLOEXEC);
|
||||||
let source_file = SourceFile::open_from(Arc::clone(&def_path), opts)?;
|
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;
|
let new_pri = pri - 1;
|
||||||
|
|
||||||
eprintln!("{last_def_line}");
|
|
||||||
|
|
||||||
let new_pri_line = append_override::get_next_prio_line(
|
let new_pri_line = append_override::get_next_prio_line(
|
||||||
source_file.clone(),
|
source_file.clone(),
|
||||||
args.name.into(),
|
args.name.into(),
|
||||||
last_def_line.clone(),
|
|
||||||
new_pri,
|
new_pri,
|
||||||
args.value.into(),
|
args.value.into(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
eprintln!("new_pri_line={new_pri_line}");
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
167
src/source.rs
167
src/source.rs
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, Ref, RefCell},
|
cell::{Ref, RefCell},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::{BufRead, BufReader, BufWriter},
|
io::{BufRead, BufReader, BufWriter},
|
||||||
iter, mem,
|
ops::Deref,
|
||||||
ops::{Deref, DerefMut},
|
ptr,
|
||||||
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError, RwLock},
|
sync::{Arc, Mutex, OnceLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Line;
|
use crate::Line;
|
||||||
|
|
@ -13,6 +13,33 @@ use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use fs_err::OpenOptions;
|
use fs_err::OpenOptions;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
pub fn replace_file<'a>(
|
||||||
|
path: &Path,
|
||||||
|
contents: impl IntoIterator<Item = &'a [u8]>,
|
||||||
|
) -> 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)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub struct SourceLine {
|
pub struct SourceLine {
|
||||||
|
|
@ -25,6 +52,30 @@ impl SourceLine {
|
||||||
pub fn text(&self) -> Arc<str> {
|
pub fn text(&self) -> Arc<str> {
|
||||||
Arc::clone(&self.text)
|
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<Path> {
|
||||||
|
Arc::clone(&self.path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SourceLine {
|
impl Display for SourceLine {
|
||||||
|
|
@ -39,11 +90,6 @@ impl Display for SourceLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
pub struct SourcePath {
|
|
||||||
path: Arc<Path>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SourceFile {
|
pub struct SourceFile {
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
|
|
@ -126,6 +172,7 @@ impl SourceFile {
|
||||||
if new_lines.is_empty() {
|
if new_lines.is_empty() {
|
||||||
return Ok(());
|
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));
|
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 cur_lines = self.lines()?;
|
||||||
let first_half = cur_lines
|
let first_half = cur_lines
|
||||||
.iter()
|
.iter()
|
||||||
.take(new_lines.last().unwrap().line.prev().index() as usize)
|
.take(num_lines_before_new)
|
||||||
.map(SourceLine::text);
|
.map(SourceLine::text);
|
||||||
let middle = new_lines.iter().map(SourceLine::text);
|
let middle = new_lines.iter().map(SourceLine::text);
|
||||||
let second_half = cur_lines
|
let second_half = cur_lines
|
||||||
.iter()
|
.iter()
|
||||||
.skip(new_lines.last().unwrap().line.prev().index() as usize)
|
.skip(num_lines_before_new)
|
||||||
.map(SourceLine::text);
|
.map(SourceLine::text);
|
||||||
|
|
||||||
let final_lines: Vec<SourceLine> = first_half
|
let final_lines: Vec<SourceLine> = first_half
|
||||||
|
|
@ -158,31 +205,11 @@ impl SourceFile {
|
||||||
|
|
||||||
drop(cur_lines);
|
drop(cur_lines);
|
||||||
|
|
||||||
// Write it to a file in the same directory.
|
let data = final_lines
|
||||||
let new_name: OsString = [
|
.iter()
|
||||||
// foo
|
.map(SourceLine::text_bytes_ref)
|
||||||
path.file_name().unwrap(),
|
.pipe(|iterator| Itertools::intersperse(iterator, b"\n"));
|
||||||
OsStr::new(".tmp"),
|
replace_file(&path, data)?;
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect::<OsString>();
|
|
||||||
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.
|
// Finally, update state.
|
||||||
self.lines.get().unwrap().replace(final_lines);
|
self.lines.get().unwrap().replace(final_lines);
|
||||||
|
|
@ -190,74 +217,6 @@ impl SourceFile {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_line(&mut self, at: Line, text: Arc<str>) -> 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<SourceLine> = 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<Line> = 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::<OsString>();
|
|
||||||
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<Path> {
|
pub fn path(&self) -> Arc<Path> {
|
||||||
Arc::clone(&self.path)
|
Arc::clone(&self.path)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue