Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Commit c517874

Browse files
committed
Make replace a rustfix module
1 parent 610d3e1 commit c517874

File tree

3 files changed

+229
-1
lines changed

3 files changed

+229
-1
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ env_logger = "0.5.0-rc.1"
2929
log = "0.4.1"
3030
pretty_assertions = "0.4.1"
3131
tempdir = "0.3.5"
32+
proptest = "0.7.0"
3233

3334
[workspace]
3435
members = [

src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#[macro_use]
22
extern crate serde_derive;
33
extern crate serde_json;
4+
#[macro_use]
45
extern crate failure;
5-
extern crate replace_rs as replace;
6+
#[cfg(test)]
7+
#[macro_use]
8+
extern crate proptest;
69

710
use std::collections::HashSet;
811
use std::ops::Range;
@@ -11,6 +14,7 @@ use failure::Error;
1114

1215
pub mod diagnostics;
1316
use diagnostics::{Diagnostic, DiagnosticSpan};
17+
mod replace;
1418

1519
pub fn get_suggestions_from_json<S: ::std::hash::BuildHasher>(
1620
input: &str,

src/replace.rs

+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
//! A small module giving you a simple container that allows easy and cheap
2+
//! replacement of parts of its content, with the ability to prevent changing
3+
//! the same parts multiple times.
4+
5+
use std::rc::Rc;
6+
use failure::Error;
7+
8+
#[derive(Debug, Clone, PartialEq, Eq)]
9+
enum State {
10+
Initial,
11+
Replaced(Rc<[u8]>),
12+
}
13+
14+
#[derive(Debug, Clone, PartialEq, Eq)]
15+
struct Span {
16+
/// Start of this span in parent data
17+
start: usize,
18+
/// up to end inculding
19+
end: usize,
20+
data: State,
21+
}
22+
23+
/// A container that allows easily replacing chunks of its data
24+
#[derive(Debug, Clone, Default)]
25+
pub struct Data {
26+
original: Vec<u8>,
27+
parts: Vec<Span>,
28+
}
29+
30+
impl Data {
31+
/// Create a new data container from a slice of bytes
32+
pub fn new(data: &[u8]) -> Self {
33+
Data {
34+
original: data.into(),
35+
parts: vec![Span {
36+
data: State::Initial,
37+
start: 0,
38+
end: data.len(),
39+
}],
40+
}
41+
}
42+
43+
/// Render this data as a vector of bytes
44+
pub fn to_vec(&self) -> Vec<u8> {
45+
self.parts.iter().fold(Vec::new(), |mut acc, d| {
46+
match d.data {
47+
State::Initial => acc.extend_from_slice(&self.original[d.start..d.end]),
48+
State::Replaced(ref d) => acc.extend_from_slice(&d),
49+
};
50+
acc
51+
})
52+
}
53+
54+
/// Replace a chunk of data with the given slice, erroring when this part
55+
/// was already changed previously.
56+
pub fn replace_range(
57+
&mut self,
58+
from: usize,
59+
up_to_and_including: usize,
60+
data: &[u8],
61+
) -> Result<(), Error> {
62+
ensure!(
63+
from <= up_to_and_including,
64+
"Invalid range {}...{}, start is larger than end",
65+
from,
66+
up_to_and_including
67+
);
68+
ensure!(
69+
up_to_and_including <= self.original.len(),
70+
"Invalid range {}...{} given, original data is only {} byte long",
71+
from,
72+
up_to_and_including,
73+
self.original.len()
74+
);
75+
76+
// Since we error out when replacing an already replaced chunk of data,
77+
// we can take some shortcuts here. For example, there can be no
78+
// overlapping replacements -- we _always_ split a chunk of 'initial'
79+
// data into three[^empty] parts, and there can't ever be two 'initial'
80+
// parts touching.
81+
//
82+
// [^empty]: Leading and trailing ones might be empty if we replace
83+
// the whole chunk. As an optimization and without loss of generality we
84+
// don't add empty parts.
85+
let new_parts = {
86+
let index_of_part_to_split = self.parts
87+
.iter()
88+
.position(|p| p.start <= from && p.end >= up_to_and_including)
89+
.ok_or_else(|| {
90+
format_err!(
91+
"Could not find data slice that covers range {}..{}",
92+
from,
93+
up_to_and_including
94+
)
95+
})?;
96+
97+
let part_to_split = &self.parts[index_of_part_to_split];
98+
ensure!(
99+
part_to_split.data == State::Initial,
100+
"Cannot replace slice of data that was already replaced"
101+
);
102+
103+
let mut new_parts = Vec::with_capacity(self.parts.len() + 2);
104+
105+
// Previous parts
106+
if let Some(ps) = self.parts.get(..index_of_part_to_split) {
107+
new_parts.extend_from_slice(&ps);
108+
}
109+
110+
// Keep initial data on left side of part
111+
if from > part_to_split.start {
112+
new_parts.push(Span {
113+
start: part_to_split.start,
114+
end: from,
115+
data: State::Initial,
116+
});
117+
}
118+
119+
// New part
120+
new_parts.push(Span {
121+
start: from,
122+
end: up_to_and_including,
123+
data: State::Replaced(data.into()),
124+
});
125+
126+
// Keep initial data on right side of part
127+
if up_to_and_including < part_to_split.end {
128+
new_parts.push(Span {
129+
start: up_to_and_including + 1,
130+
end: part_to_split.end,
131+
data: State::Initial,
132+
});
133+
}
134+
135+
// Following parts
136+
if let Some(ps) = self.parts.get(index_of_part_to_split + 1..) {
137+
new_parts.extend_from_slice(&ps);
138+
}
139+
140+
new_parts
141+
};
142+
143+
self.parts = new_parts;
144+
145+
Ok(())
146+
}
147+
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
use super::*;
152+
use proptest::prelude::*;
153+
154+
fn str(i: &[u8]) -> &str {
155+
::std::str::from_utf8(i).unwrap()
156+
}
157+
158+
#[test]
159+
fn replace_some_stuff() {
160+
let mut d = Data::new(b"foo bar baz");
161+
d.replace_range(4, 6, b"lol").unwrap();
162+
assert_eq!("foo lol baz", str(&d.to_vec()));
163+
}
164+
165+
#[test]
166+
fn replace_a_single_char() {
167+
let mut d = Data::new(b"let y = true;");
168+
d.replace_range(4, 4, b"mut y").unwrap();
169+
assert_eq!("let mut y = true;", str(&d.to_vec()));
170+
}
171+
172+
#[test]
173+
fn replace_multiple_lines() {
174+
let mut d = Data::new(b"lorem\nipsum\ndolor");
175+
176+
d.replace_range(6, 10, b"lol").unwrap();
177+
assert_eq!("lorem\nlol\ndolor", str(&d.to_vec()));
178+
179+
d.replace_range(12, 17, b"lol").unwrap();
180+
assert_eq!("lorem\nlol\nlol", str(&d.to_vec()));
181+
}
182+
183+
#[test]
184+
#[should_panic(expected = "Cannot replace slice of data that was already replaced")]
185+
fn replace_overlapping_stuff_errs() {
186+
let mut d = Data::new(b"foo bar baz");
187+
188+
d.replace_range(4, 6, b"lol").unwrap();
189+
assert_eq!("foo lol baz", str(&d.to_vec()));
190+
191+
d.replace_range(4, 6, b"lol").unwrap();
192+
}
193+
194+
#[test]
195+
#[should_panic(expected = "original data is only 3 byte long")]
196+
fn broken_replacements() {
197+
let mut d = Data::new(b"foo");
198+
d.replace_range(4, 7, b"lol").unwrap();
199+
}
200+
201+
proptest! {
202+
#[test]
203+
#[ignore]
204+
fn new_to_vec_roundtrip(ref s in "\\PC*") {
205+
assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
206+
}
207+
208+
#[test]
209+
#[ignore]
210+
fn replace_random_chunks(
211+
ref data in "\\PC*",
212+
ref replacements in prop::collection::vec(
213+
(any::<::std::ops::Range<usize>>(), any::<Vec<u8>>()),
214+
1..1337,
215+
)
216+
) {
217+
let mut d = Data::new(data.as_bytes());
218+
for &(ref range, ref bytes) in replacements {
219+
let _ = d.replace_range(range.start, range.end, bytes);
220+
}
221+
}
222+
}
223+
}

0 commit comments

Comments
 (0)