cow_rewrite/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5use alloc::{
6    borrow::{Cow, ToOwned},
7    string::String,
8};
9
10mod private {
11    pub trait Sealed {}
12
13    pub trait WithInfo {
14        type Info: Default;
15    }
16}
17use private::{Sealed, WithInfo};
18
19pub trait Rewritable: AsRef<Self::Ref> + Sealed {
20    type Ref: ToOwned + WithInfo + ?Sized;
21}
22
23pub struct Rewrite<T: Rewritable> {
24    input: T,
25    output: <T::Ref as ToOwned>::Owned,
26    copied: bool,
27    info: <T::Ref as WithInfo>::Info,
28}
29
30/// Check that bytes `a` contains bytes `b` at offset `i`
31fn is_bytes_at(a: &[u8], i: usize, b: &[u8]) -> bool {
32    let l = b.len();
33    unsafe { a.len() >= i + l && a.get_unchecked(i..(i + l)) == b }
34}
35
36/// Check that string slice `s` contains char `c` at byte offset `i`
37fn is_char_at(s: &str, i: usize, c: char) -> bool {
38    let mut d = [0; size_of::<char>()];
39    c.encode_utf8(&mut d);
40    let l = c.len_utf8();
41    let d = unsafe { &d.get_unchecked(0..l) };
42    is_bytes_at(s.as_bytes(), i, d)
43}
44
45impl WithInfo for str {
46    type Info = usize;
47}
48
49impl<'a> Sealed for &'a str {}
50impl<'a> Rewritable for &'a str {
51    type Ref = str;
52}
53
54impl Sealed for String {}
55impl Rewritable for String {
56    type Ref = str;
57}
58
59impl<'a> Sealed for Cow<'a, str> {}
60impl<'a> Rewritable for Cow<'a, str> {
61    type Ref = str;
62}
63
64impl<'a, 'b> Sealed for &'b Cow<'a, str> {}
65impl<'a, 'b> Rewritable for &'b Cow<'a, str> {
66    type Ref = str;
67}
68
69impl<T: Rewritable> Rewrite<T>
70where
71    <T::Ref as ToOwned>::Owned: Default,
72{
73    pub fn new(input: T) -> Self {
74        Self {
75            input,
76            output: Default::default(),
77            copied: false,
78            info: Default::default(),
79        }
80    }
81}
82
83impl<T: Rewritable<Ref = str>> Rewrite<T> {
84    fn copy(&mut self) {
85        if !self.copied {
86            self.output.push_str(&self.input.as_ref()[0..self.info]);
87            self.copied = true;
88        }
89    }
90    pub fn push(&mut self, c: char) {
91        if !self.copied && is_char_at(self.input.as_ref(), self.info, c) {
92            self.info += c.len_utf8();
93        } else {
94            self.copy();
95            self.output.push(c);
96        }
97    }
98    pub fn push_str(&mut self, s: &str) {
99        if !self.copied && is_bytes_at(self.input.as_ref().as_bytes(), self.info, s.as_bytes()) {
100            self.info += s.len();
101        } else {
102            self.copy();
103            self.output.push_str(s);
104        }
105    }
106}
107
108impl<'a> From<Rewrite<&'a str>> for Cow<'a, str> {
109    fn from(this: Rewrite<&'a str>) -> Self {
110        if this.copied {
111            Cow::Owned(this.output)
112        } else {
113            Cow::Borrowed(&this.input[0..this.info])
114        }
115    }
116}
117
118impl From<Rewrite<String>> for String {
119    fn from(this: Rewrite<String>) -> Self {
120        if this.copied {
121            this.output
122        } else {
123            let mut s = this.input;
124            s.truncate(this.info);
125            s
126        }
127    }
128}
129
130impl<'a> From<Rewrite<Cow<'a, str>>> for Cow<'a, str> {
131    fn from(this: Rewrite<Cow<'a, str>>) -> Self {
132        if this.copied {
133            Cow::Owned(this.output)
134        } else {
135            match this.input {
136                Cow::Borrowed(s) => Cow::Borrowed(&s[0..this.info]),
137                Cow::Owned(mut s) => {
138                    s.truncate(this.info);
139                    Cow::Owned(s)
140                }
141            }
142        }
143    }
144}
145
146impl<'a, 'b> From<Rewrite<&'b Cow<'a, str>>> for Cow<'a, str> {
147    fn from(this: Rewrite<&'b Cow<'a, str>>) -> Self {
148        if this.copied {
149            Cow::Owned(this.output)
150        } else {
151            match this.input {
152                Cow::Borrowed(s) => Cow::Borrowed(&s[0..this.info]),
153                Cow::Owned(s) => {
154                    let mut d = String::with_capacity(this.info);
155                    d.push_str(&s[0..this.info]);
156                    Cow::Owned(d)
157                }
158            }
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn str_write_same() {
169        let s = "abc";
170        let mut r = Rewrite::new(s);
171        r.push('a');
172        r.push_str("bc");
173        let d: Cow<str> = r.into();
174        assert_eq!(d, Cow::Borrowed(s))
175    }
176
177    #[test]
178    fn str_write_less() {
179        let s = "abc";
180        let mut r = Rewrite::new(s);
181        r.push('a');
182        r.push_str("b");
183        let d: Cow<str> = r.into();
184        assert_eq!(d, Cow::Borrowed(&s[..2]))
185    }
186
187    #[test]
188    fn str_write_more() {
189        let s = "abc";
190        let mut r = Rewrite::new(s);
191        r.push('a');
192        r.push_str("bcd");
193        let d: Cow<str> = r.into();
194        assert_eq!(d, Cow::<str>::Owned("abcd".to_owned()))
195    }
196
197    #[test]
198    fn str_write_different() {
199        let s = "abc";
200        let mut r = Rewrite::new(s);
201        r.push('a');
202        r.push_str("bd");
203        let d: Cow<str> = r.into();
204        assert_eq!(d, Cow::<str>::Owned("abd".to_owned()))
205    }
206}