/rust/registry/src/index.crates.io-6f17d22bba15001f/fancy-regex-0.11.0/src/expand.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use crate::parse::{parse_decimal, parse_id}; |
2 | | use crate::{Captures, CompileError, Error, ParseError, Regex}; |
3 | | use std::borrow::Cow; |
4 | | use std::io; |
5 | | use std::mem; |
6 | | |
7 | | /// A set of options for expanding a template string using the contents |
8 | | /// of capture groups. |
9 | | #[derive(Debug)] |
10 | | pub struct Expander { |
11 | | sub_char: char, |
12 | | open: &'static str, |
13 | | close: &'static str, |
14 | | allow_undelimited_name: bool, |
15 | | } |
16 | | |
17 | | impl Default for Expander { |
18 | | /// Returns the default expander used by [`Captures::expand`]. |
19 | | /// |
20 | | /// [`Captures::expand`]: struct.Captures.html#expand |
21 | 0 | fn default() -> Self { |
22 | 0 | Expander { |
23 | 0 | sub_char: '$', |
24 | 0 | open: "{", |
25 | 0 | close: "}", |
26 | 0 | allow_undelimited_name: true, |
27 | 0 | } |
28 | 0 | } |
29 | | } |
30 | | |
31 | | impl Expander { |
32 | | /// Returns an expander that uses Python-compatible syntax. |
33 | | /// |
34 | | /// Expands all instances of `\num` or `\g<name>` in `replacement` |
35 | | /// to the corresponding capture group `num` or `name`, and writes |
36 | | /// them to the `dst` buffer given. |
37 | | /// |
38 | | /// `name` may be an integer corresponding to the index of the |
39 | | /// capture group (counted by order of opening parenthesis where `\0` is the |
40 | | /// entire match) or it can be a name (consisting of letters, digits or |
41 | | /// underscores) corresponding to a named capture group. |
42 | | /// |
43 | | /// `num` must be an integer corresponding to the index of the |
44 | | /// capture group. |
45 | | /// |
46 | | /// If `num` or `name` isn't a valid capture group (whether the name doesn't exist |
47 | | /// or isn't a valid index), then it is replaced with the empty string. |
48 | | /// |
49 | | /// The longest possible number is used. e.g., `\10` looks up capture |
50 | | /// group 10 and not capture group 1 followed by a literal 0. |
51 | | /// |
52 | | /// To write a literal `\`, use `\\`. |
53 | 0 | pub fn python() -> Expander { |
54 | 0 | Expander { |
55 | 0 | sub_char: '\\', |
56 | 0 | open: "g<", |
57 | 0 | close: ">", |
58 | 0 | allow_undelimited_name: false, |
59 | 0 | } |
60 | 0 | } |
61 | | |
62 | | /// Checks `template` for errors. The following conditions are checked for: |
63 | | /// |
64 | | /// - A reference to a numbered group that does not exist in `regex` |
65 | | /// - A reference to a numbered group (other than 0) when `regex` contains named groups |
66 | | /// - A reference to a named group that does not occur in `regex` |
67 | | /// - An opening group name delimiter without a closing delimiter |
68 | | /// - Using an empty string as a group name |
69 | 0 | pub fn check(&self, template: &str, regex: &Regex) -> crate::Result<()> { |
70 | 0 | let on_group_num = |num| { |
71 | 0 | if num == 0 { |
72 | 0 | Ok(()) |
73 | 0 | } else if !regex.named_groups.is_empty() { |
74 | 0 | Err(Error::CompileError(CompileError::NamedBackrefOnly)) |
75 | 0 | } else if num < regex.captures_len() { |
76 | 0 | Ok(()) |
77 | | } else { |
78 | 0 | Err(Error::CompileError(CompileError::InvalidBackref)) |
79 | | } |
80 | 0 | }; |
81 | 0 | self.exec(template, |step| match step { |
82 | 0 | Step::Char(_) => Ok(()), |
83 | 0 | Step::GroupName(name) => { |
84 | 0 | if regex.named_groups.contains_key(name) { |
85 | 0 | Ok(()) |
86 | 0 | } else if let Ok(num) = name.parse() { |
87 | 0 | on_group_num(num) |
88 | | } else { |
89 | 0 | Err(Error::CompileError(CompileError::InvalidBackref)) |
90 | | } |
91 | | } |
92 | 0 | Step::GroupNum(num) => on_group_num(num), |
93 | 0 | Step::Error => Err(Error::ParseError( |
94 | 0 | 0, |
95 | 0 | ParseError::GeneralParseError( |
96 | 0 | "parse error in template while expanding".to_string(), |
97 | 0 | ), |
98 | 0 | )), |
99 | 0 | }) |
100 | 0 | } |
101 | | |
102 | | /// Escapes the substitution character in `text` so it appears literally |
103 | | /// in the output of `expansion`. |
104 | | /// |
105 | | /// ``` |
106 | | /// assert_eq!( |
107 | | /// fancy_regex::Expander::default().escape("Has a literal $ sign."), |
108 | | /// "Has a literal $$ sign.", |
109 | | /// ); |
110 | | /// ``` |
111 | 0 | pub fn escape<'a>(&self, text: &'a str) -> Cow<'a, str> { |
112 | 0 | if text.contains(self.sub_char) { |
113 | 0 | let mut quoted = String::with_capacity(self.sub_char.len_utf8() * 2); |
114 | 0 | quoted.push(self.sub_char); |
115 | 0 | quoted.push(self.sub_char); |
116 | 0 | Cow::Owned(text.replace(self.sub_char, "ed)) |
117 | | } else { |
118 | 0 | Cow::Borrowed(text) |
119 | | } |
120 | 0 | } |
121 | | |
122 | | #[doc(hidden)] |
123 | | #[deprecated(since = "0.4.0", note = "Use `escape` instead.")] |
124 | 0 | pub fn quote<'a>(&self, text: &'a str) -> Cow<'a, str> { |
125 | 0 | self.escape(text) |
126 | 0 | } |
127 | | |
128 | | /// Expands the template string `template` using the syntax defined |
129 | | /// by this expander and the values of capture groups from `captures`. |
130 | 0 | pub fn expansion(&self, template: &str, captures: &Captures<'_>) -> String { |
131 | 0 | let mut cursor = io::Cursor::new(Vec::with_capacity(template.len())); |
132 | 0 | self.write_expansion(&mut cursor, template, captures) |
133 | 0 | .expect("expansion succeeded"); |
134 | 0 | String::from_utf8(cursor.into_inner()).expect("expansion is UTF-8") |
135 | 0 | } |
136 | | |
137 | | /// Appends the expansion produced by `expansion` to `dst`. Potentially more efficient |
138 | | /// than calling `expansion` directly and appending to an existing string. |
139 | 0 | pub fn append_expansion(&self, dst: &mut String, template: &str, captures: &Captures<'_>) { |
140 | 0 | let pos = dst.len(); |
141 | 0 | let mut cursor = io::Cursor::new(mem::replace(dst, String::new()).into_bytes()); |
142 | 0 | cursor.set_position(pos as u64); |
143 | 0 | self.write_expansion(&mut cursor, template, captures) |
144 | 0 | .expect("expansion succeeded"); |
145 | 0 | *dst = String::from_utf8(cursor.into_inner()).expect("expansion is UTF-8"); |
146 | 0 | } |
147 | | |
148 | | /// Writes the expansion produced by `expansion` to `dst`. Potentially more efficient |
149 | | /// than calling `expansion` directly and writing the result. |
150 | 0 | pub fn write_expansion( |
151 | 0 | &self, |
152 | 0 | mut dst: impl io::Write, |
153 | 0 | template: &str, |
154 | 0 | captures: &Captures<'_>, |
155 | 0 | ) -> io::Result<()> { |
156 | 0 | self.exec(template, |step| match step { |
157 | 0 | Step::Char(c) => write!(dst, "{}", c), |
158 | 0 | Step::GroupName(name) => { |
159 | 0 | if let Some(m) = captures.name(name) { |
160 | 0 | write!(dst, "{}", m.as_str()) |
161 | 0 | } else if let Some(m) = name.parse().ok().and_then(|num| captures.get(num)) { |
162 | 0 | write!(dst, "{}", m.as_str()) |
163 | | } else { |
164 | 0 | Ok(()) |
165 | | } |
166 | | } |
167 | 0 | Step::GroupNum(num) => { |
168 | 0 | if let Some(m) = captures.get(num) { |
169 | 0 | write!(dst, "{}", m.as_str()) |
170 | | } else { |
171 | 0 | Ok(()) |
172 | | } |
173 | | } |
174 | 0 | Step::Error => Ok(()), |
175 | 0 | }) |
176 | 0 | } |
177 | | |
178 | 0 | fn exec<'t, E>( |
179 | 0 | &self, |
180 | 0 | template: &'t str, |
181 | 0 | mut f: impl FnMut(Step<'t>) -> Result<(), E>, |
182 | 0 | ) -> Result<(), E> { |
183 | 0 | debug_assert!(!self.open.is_empty()); |
184 | 0 | debug_assert!(!self.close.is_empty()); |
185 | 0 | let mut iter = template.chars(); |
186 | 0 | while let Some(c) = iter.next() { |
187 | 0 | if c == self.sub_char { |
188 | 0 | let tail = iter.as_str(); |
189 | 0 | let skip = if tail.starts_with(self.sub_char) { |
190 | 0 | f(Step::Char(self.sub_char))?; |
191 | 0 | 1 |
192 | 0 | } else if let Some((id, skip)) = |
193 | 0 | parse_id(tail, self.open, self.close).or_else(|| { |
194 | 0 | if self.allow_undelimited_name { |
195 | 0 | parse_id(tail, "", "") |
196 | | } else { |
197 | 0 | None |
198 | | } |
199 | 0 | }) Unexecuted instantiation: <fancy_regex::expand::Expander>::exec::<fancy_regex::error::Error, <fancy_regex::expand::Expander>::check::{closure#1}>::{closure#0} Unexecuted instantiation: <fancy_regex::expand::Expander>::exec::<std::io::error::Error, <fancy_regex::expand::Expander>::write_expansion<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}>::{closure#0} |
200 | | { |
201 | 0 | f(Step::GroupName(id))?; |
202 | 0 | skip |
203 | 0 | } else if let Some((skip, num)) = parse_decimal(tail, 0) { |
204 | 0 | f(Step::GroupNum(num))?; |
205 | 0 | skip |
206 | | } else { |
207 | 0 | f(Step::Error)?; |
208 | 0 | f(Step::Char(self.sub_char))?; |
209 | 0 | 0 |
210 | | }; |
211 | 0 | iter = iter.as_str()[skip..].chars(); |
212 | | } else { |
213 | 0 | f(Step::Char(c))?; |
214 | | } |
215 | | } |
216 | 0 | Ok(()) |
217 | 0 | } Unexecuted instantiation: <fancy_regex::expand::Expander>::exec::<fancy_regex::error::Error, <fancy_regex::expand::Expander>::check::{closure#1}> Unexecuted instantiation: <fancy_regex::expand::Expander>::exec::<std::io::error::Error, <fancy_regex::expand::Expander>::write_expansion<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}> |
218 | | } |
219 | | |
220 | | enum Step<'a> { |
221 | | Char(char), |
222 | | GroupName(&'a str), |
223 | | GroupNum(usize), |
224 | | Error, |
225 | | } |