##// END OF EJS Templates
rhg: Ignore trailing newlines in .hg/sharedpath...
Simon Sapin -
r47427:e8cd519a default
parent child Browse files
Show More
@@ -1,263 +1,264 b''
1 1 use crate::config::{Config, ConfigError, ConfigParseError};
2 2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
3 3 use crate::requirements;
4 use crate::utils::current_dir;
5 4 use crate::utils::files::get_path_from_bytes;
5 use crate::utils::{current_dir, SliceExt};
6 6 use memmap::{Mmap, MmapOptions};
7 7 use std::collections::HashSet;
8 8 use std::path::{Path, PathBuf};
9 9
10 10 /// A repository on disk
11 11 pub struct Repo {
12 12 working_directory: PathBuf,
13 13 dot_hg: PathBuf,
14 14 store: PathBuf,
15 15 requirements: HashSet<String>,
16 16 config: Config,
17 17 }
18 18
19 19 #[derive(Debug, derive_more::From)]
20 20 pub enum RepoError {
21 21 NotFound {
22 22 at: PathBuf,
23 23 },
24 24 #[from]
25 25 ConfigParseError(ConfigParseError),
26 26 #[from]
27 27 Other(HgError),
28 28 }
29 29
30 30 impl From<ConfigError> for RepoError {
31 31 fn from(error: ConfigError) -> Self {
32 32 match error {
33 33 ConfigError::Parse(error) => error.into(),
34 34 ConfigError::Other(error) => error.into(),
35 35 }
36 36 }
37 37 }
38 38
39 39 /// Filesystem access abstraction for the contents of a given "base" diretory
40 40 #[derive(Clone, Copy)]
41 41 pub struct Vfs<'a> {
42 42 pub(crate) base: &'a Path,
43 43 }
44 44
45 45 impl Repo {
46 46 /// Find a repository, either at the given path (which must contain a `.hg`
47 47 /// sub-directory) or by searching the current directory and its
48 48 /// ancestors.
49 49 ///
50 50 /// A method with two very different "modes" like this usually a code smell
51 51 /// to make two methods instead, but in this case an `Option` is what rhg
52 52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
53 53 /// Having two methods would just move that `if` to almost all callers.
54 54 pub fn find(
55 55 config: &Config,
56 56 explicit_path: Option<&Path>,
57 57 ) -> Result<Self, RepoError> {
58 58 if let Some(root) = explicit_path {
59 59 // Having an absolute path isn’t necessary here but can help code
60 60 // elsewhere
61 61 let root = current_dir()?.join(root);
62 62 if root.join(".hg").is_dir() {
63 63 Self::new_at_path(root, config)
64 64 } else {
65 65 Err(RepoError::NotFound {
66 66 at: root.to_owned(),
67 67 })
68 68 }
69 69 } else {
70 70 let current_directory = crate::utils::current_dir()?;
71 71 // ancestors() is inclusive: it first yields `current_directory`
72 72 // as-is.
73 73 for ancestor in current_directory.ancestors() {
74 74 if ancestor.join(".hg").is_dir() {
75 75 return Self::new_at_path(ancestor.to_owned(), config);
76 76 }
77 77 }
78 78 Err(RepoError::NotFound {
79 79 at: current_directory,
80 80 })
81 81 }
82 82 }
83 83
84 84 /// To be called after checking that `.hg` is a sub-directory
85 85 fn new_at_path(
86 86 working_directory: PathBuf,
87 87 config: &Config,
88 88 ) -> Result<Self, RepoError> {
89 89 let dot_hg = working_directory.join(".hg");
90 90
91 91 let mut repo_config_files = Vec::new();
92 92 repo_config_files.push(dot_hg.join("hgrc"));
93 93 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
94 94
95 95 let hg_vfs = Vfs { base: &dot_hg };
96 96 let mut reqs = requirements::load_if_exists(hg_vfs)?;
97 97 let relative =
98 98 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
99 99 let shared =
100 100 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
101 101
102 102 // From `mercurial/localrepo.py`:
103 103 //
104 104 // if .hg/requires contains the sharesafe requirement, it means
105 105 // there exists a `.hg/store/requires` too and we should read it
106 106 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
107 107 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
108 108 // is not present, refer checkrequirementscompat() for that
109 109 //
110 110 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
111 111 // repository was shared the old way. We check the share source
112 112 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
113 113 // current repository needs to be reshared
114 114 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
115 115
116 116 let store_path;
117 117 if !shared {
118 118 store_path = dot_hg.join("store");
119 119 } else {
120 120 let bytes = hg_vfs.read("sharedpath")?;
121 let mut shared_path = get_path_from_bytes(&bytes).to_owned();
121 let mut shared_path =
122 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
122 123 if relative {
123 124 shared_path = dot_hg.join(shared_path)
124 125 }
125 126 if !shared_path.is_dir() {
126 127 return Err(HgError::corrupted(format!(
127 128 ".hg/sharedpath points to nonexistent directory {}",
128 129 shared_path.display()
129 130 ))
130 131 .into());
131 132 }
132 133
133 134 store_path = shared_path.join("store");
134 135
135 136 let source_is_share_safe =
136 137 requirements::load(Vfs { base: &shared_path })?
137 138 .contains(requirements::SHARESAFE_REQUIREMENT);
138 139
139 140 if share_safe && !source_is_share_safe {
140 141 return Err(match config
141 142 .get(b"safe-mismatch", b"source-not-safe")
142 143 {
143 144 Some(b"abort") | None => HgError::abort(
144 145 "share source does not support share-safe requirement",
145 146 ),
146 147 _ => HgError::unsupported("share-safe downgrade"),
147 148 }
148 149 .into());
149 150 } else if source_is_share_safe && !share_safe {
150 151 return Err(
151 152 match config.get(b"safe-mismatch", b"source-safe") {
152 153 Some(b"abort") | None => HgError::abort(
153 154 "version mismatch: source uses share-safe \
154 155 functionality while the current share does not",
155 156 ),
156 157 _ => HgError::unsupported("share-safe upgrade"),
157 158 }
158 159 .into(),
159 160 );
160 161 }
161 162
162 163 if share_safe {
163 164 repo_config_files.insert(0, shared_path.join("hgrc"))
164 165 }
165 166 }
166 167 if share_safe {
167 168 reqs.extend(requirements::load(Vfs { base: &store_path })?);
168 169 }
169 170
170 171 let repo_config = config.combine_with_repo(&repo_config_files)?;
171 172
172 173 let repo = Self {
173 174 requirements: reqs,
174 175 working_directory,
175 176 store: store_path,
176 177 dot_hg,
177 178 config: repo_config,
178 179 };
179 180
180 181 requirements::check(&repo)?;
181 182
182 183 Ok(repo)
183 184 }
184 185
185 186 pub fn working_directory_path(&self) -> &Path {
186 187 &self.working_directory
187 188 }
188 189
189 190 pub fn requirements(&self) -> &HashSet<String> {
190 191 &self.requirements
191 192 }
192 193
193 194 pub fn config(&self) -> &Config {
194 195 &self.config
195 196 }
196 197
197 198 /// For accessing repository files (in `.hg`), except for the store
198 199 /// (`.hg/store`).
199 200 pub fn hg_vfs(&self) -> Vfs<'_> {
200 201 Vfs { base: &self.dot_hg }
201 202 }
202 203
203 204 /// For accessing repository store files (in `.hg/store`)
204 205 pub fn store_vfs(&self) -> Vfs<'_> {
205 206 Vfs { base: &self.store }
206 207 }
207 208
208 209 /// For accessing the working copy
209 210
210 211 // The undescore prefix silences the "never used" warning. Remove before
211 212 // using.
212 213 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
213 214 Vfs {
214 215 base: &self.working_directory,
215 216 }
216 217 }
217 218
218 219 pub fn dirstate_parents(
219 220 &self,
220 221 ) -> Result<crate::dirstate::DirstateParents, HgError> {
221 222 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
222 223 let parents =
223 224 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
224 225 Ok(parents.clone())
225 226 }
226 227 }
227 228
228 229 impl Vfs<'_> {
229 230 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
230 231 self.base.join(relative_path)
231 232 }
232 233
233 234 pub fn read(
234 235 &self,
235 236 relative_path: impl AsRef<Path>,
236 237 ) -> Result<Vec<u8>, HgError> {
237 238 let path = self.join(relative_path);
238 239 std::fs::read(&path).when_reading_file(&path)
239 240 }
240 241
241 242 pub fn mmap_open(
242 243 &self,
243 244 relative_path: impl AsRef<Path>,
244 245 ) -> Result<Mmap, HgError> {
245 246 let path = self.base.join(relative_path);
246 247 let file = std::fs::File::open(&path).when_reading_file(&path)?;
247 248 // TODO: what are the safety requirements here?
248 249 let mmap = unsafe { MmapOptions::new().map(&file) }
249 250 .when_reading_file(&path)?;
250 251 Ok(mmap)
251 252 }
252 253
253 254 pub fn rename(
254 255 &self,
255 256 relative_from: impl AsRef<Path>,
256 257 relative_to: impl AsRef<Path>,
257 258 ) -> Result<(), HgError> {
258 259 let from = self.join(relative_from);
259 260 let to = self.join(relative_to);
260 261 std::fs::rename(&from, &to)
261 262 .with_context(|| IoErrorContext::RenamingFile { from, to })
262 263 }
263 264 }
@@ -1,422 +1,430 b''
1 1 // utils module
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Contains useful functions, traits, structs, etc. for use in core.
9 9
10 10 use crate::errors::{HgError, IoErrorContext};
11 11 use crate::utils::hg_path::HgPath;
12 12 use im_rc::ordmap::DiffItem;
13 13 use im_rc::ordmap::OrdMap;
14 14 use std::cell::Cell;
15 15 use std::fmt;
16 16 use std::{io::Write, ops::Deref};
17 17
18 18 pub mod files;
19 19 pub mod hg_path;
20 20 pub mod path_auditor;
21 21
22 22 /// Useful until rust/issues/56345 is stable
23 23 ///
24 24 /// # Examples
25 25 ///
26 26 /// ```
27 27 /// use crate::hg::utils::find_slice_in_slice;
28 28 ///
29 29 /// let haystack = b"This is the haystack".to_vec();
30 30 /// assert_eq!(find_slice_in_slice(&haystack, b"the"), Some(8));
31 31 /// assert_eq!(find_slice_in_slice(&haystack, b"not here"), None);
32 32 /// ```
33 33 pub fn find_slice_in_slice<T>(slice: &[T], needle: &[T]) -> Option<usize>
34 34 where
35 35 for<'a> &'a [T]: PartialEq,
36 36 {
37 37 slice
38 38 .windows(needle.len())
39 39 .position(|window| window == needle)
40 40 }
41 41
42 42 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
43 43 ///
44 44 /// # Examples
45 45 ///
46 46 /// ```
47 47 /// use crate::hg::utils::replace_slice;
48 48 /// let mut line = b"I hate writing tests!".to_vec();
49 49 /// replace_slice(&mut line, b"hate", b"love");
50 50 /// assert_eq!(
51 51 /// line,
52 52 /// b"I love writing tests!".to_vec()
53 53 /// );
54 54 /// ```
55 55 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
56 56 where
57 57 T: Clone + PartialEq,
58 58 {
59 59 if buf.len() < from.len() || from.len() != to.len() {
60 60 return;
61 61 }
62 62 for i in 0..=buf.len() - from.len() {
63 63 if buf[i..].starts_with(from) {
64 64 buf[i..(i + from.len())].clone_from_slice(to);
65 65 }
66 66 }
67 67 }
68 68
69 69 pub trait SliceExt {
70 fn trim_end_newlines(&self) -> &Self;
70 71 fn trim_end(&self) -> &Self;
71 72 fn trim_start(&self) -> &Self;
72 73 fn trim(&self) -> &Self;
73 74 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
74 75 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>;
75 76 }
76 77
77 78 #[allow(clippy::trivially_copy_pass_by_ref)]
78 79 fn is_not_whitespace(c: &u8) -> bool {
79 80 !(*c as char).is_whitespace()
80 81 }
81 82
82 83 impl SliceExt for [u8] {
84 fn trim_end_newlines(&self) -> &[u8] {
85 if let Some(last) = self.iter().rposition(|&byte| byte != b'\n') {
86 &self[..=last]
87 } else {
88 &[]
89 }
90 }
83 91 fn trim_end(&self) -> &[u8] {
84 92 if let Some(last) = self.iter().rposition(is_not_whitespace) {
85 93 &self[..=last]
86 94 } else {
87 95 &[]
88 96 }
89 97 }
90 98 fn trim_start(&self) -> &[u8] {
91 99 if let Some(first) = self.iter().position(is_not_whitespace) {
92 100 &self[first..]
93 101 } else {
94 102 &[]
95 103 }
96 104 }
97 105
98 106 /// ```
99 107 /// use hg::utils::SliceExt;
100 108 /// assert_eq!(
101 109 /// b" to trim ".trim(),
102 110 /// b"to trim"
103 111 /// );
104 112 /// assert_eq!(
105 113 /// b"to trim ".trim(),
106 114 /// b"to trim"
107 115 /// );
108 116 /// assert_eq!(
109 117 /// b" to trim".trim(),
110 118 /// b"to trim"
111 119 /// );
112 120 /// ```
113 121 fn trim(&self) -> &[u8] {
114 122 self.trim_start().trim_end()
115 123 }
116 124
117 125 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
118 126 if self.starts_with(needle) {
119 127 Some(&self[needle.len()..])
120 128 } else {
121 129 None
122 130 }
123 131 }
124 132
125 133 fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> {
126 134 let mut iter = self.splitn(2, |&byte| byte == separator);
127 135 let a = iter.next()?;
128 136 let b = iter.next()?;
129 137 Some((a, b))
130 138 }
131 139 }
132 140
133 141 pub trait Escaped {
134 142 /// Return bytes escaped for display to the user
135 143 fn escaped_bytes(&self) -> Vec<u8>;
136 144 }
137 145
138 146 impl Escaped for u8 {
139 147 fn escaped_bytes(&self) -> Vec<u8> {
140 148 let mut acc = vec![];
141 149 match self {
142 150 c @ b'\'' | c @ b'\\' => {
143 151 acc.push(b'\\');
144 152 acc.push(*c);
145 153 }
146 154 b'\t' => {
147 155 acc.extend(br"\\t");
148 156 }
149 157 b'\n' => {
150 158 acc.extend(br"\\n");
151 159 }
152 160 b'\r' => {
153 161 acc.extend(br"\\r");
154 162 }
155 163 c if (*c < b' ' || *c >= 127) => {
156 164 write!(acc, "\\x{:x}", self).unwrap();
157 165 }
158 166 c => {
159 167 acc.push(*c);
160 168 }
161 169 }
162 170 acc
163 171 }
164 172 }
165 173
166 174 impl<'a, T: Escaped> Escaped for &'a [T] {
167 175 fn escaped_bytes(&self) -> Vec<u8> {
168 176 self.iter().flat_map(Escaped::escaped_bytes).collect()
169 177 }
170 178 }
171 179
172 180 impl<T: Escaped> Escaped for Vec<T> {
173 181 fn escaped_bytes(&self) -> Vec<u8> {
174 182 self.deref().escaped_bytes()
175 183 }
176 184 }
177 185
178 186 impl<'a> Escaped for &'a HgPath {
179 187 fn escaped_bytes(&self) -> Vec<u8> {
180 188 self.as_bytes().escaped_bytes()
181 189 }
182 190 }
183 191
184 192 // TODO: use the str method when we require Rust 1.45
185 193 pub(crate) fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> {
186 194 if s.ends_with(suffix) {
187 195 Some(&s[..s.len() - suffix.len()])
188 196 } else {
189 197 None
190 198 }
191 199 }
192 200
193 201 #[cfg(unix)]
194 202 pub fn shell_quote(value: &[u8]) -> Vec<u8> {
195 203 // TODO: Use the `matches!` macro when we require Rust 1.42+
196 204 if value.iter().all(|&byte| match byte {
197 205 b'a'..=b'z'
198 206 | b'A'..=b'Z'
199 207 | b'0'..=b'9'
200 208 | b'.'
201 209 | b'_'
202 210 | b'/'
203 211 | b'+'
204 212 | b'-' => true,
205 213 _ => false,
206 214 }) {
207 215 value.to_owned()
208 216 } else {
209 217 let mut quoted = Vec::with_capacity(value.len() + 2);
210 218 quoted.push(b'\'');
211 219 for &byte in value {
212 220 if byte == b'\'' {
213 221 quoted.push(b'\\');
214 222 }
215 223 quoted.push(byte);
216 224 }
217 225 quoted.push(b'\'');
218 226 quoted
219 227 }
220 228 }
221 229
222 230 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
223 231 std::env::current_dir().map_err(|error| HgError::IoError {
224 232 error,
225 233 context: IoErrorContext::CurrentDir,
226 234 })
227 235 }
228 236
229 237 pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
230 238 std::env::current_exe().map_err(|error| HgError::IoError {
231 239 error,
232 240 context: IoErrorContext::CurrentExe,
233 241 })
234 242 }
235 243
236 244 pub(crate) enum MergeResult<V> {
237 245 UseLeftValue,
238 246 UseRightValue,
239 247 UseNewValue(V),
240 248 }
241 249
242 250 /// Return the union of the two given maps,
243 251 /// calling `merge(key, left_value, right_value)` to resolve keys that exist in
244 252 /// both.
245 253 ///
246 254 /// CC https://github.com/bodil/im-rs/issues/166
247 255 pub(crate) fn ordmap_union_with_merge<K, V>(
248 256 left: OrdMap<K, V>,
249 257 right: OrdMap<K, V>,
250 258 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
251 259 ) -> OrdMap<K, V>
252 260 where
253 261 K: Clone + Ord,
254 262 V: Clone + PartialEq,
255 263 {
256 264 if left.ptr_eq(&right) {
257 265 // One of the two maps is an unmodified clone of the other
258 266 left
259 267 } else if left.len() / 2 > right.len() {
260 268 // When two maps have different sizes,
261 269 // their size difference is a lower bound on
262 270 // how many keys of the larger map are not also in the smaller map.
263 271 // This in turn is a lower bound on the number of differences in
264 272 // `OrdMap::diff` and the "amount of work" that would be done
265 273 // by `ordmap_union_with_merge_by_diff`.
266 274 //
267 275 // Here `left` is more than twice the size of `right`,
268 276 // so the number of differences is more than the total size of
269 277 // `right`. Therefore an algorithm based on iterating `right`
270 278 // is more efficient.
271 279 //
272 280 // This helps a lot when a tiny (or empty) map is merged
273 281 // with a large one.
274 282 ordmap_union_with_merge_by_iter(left, right, merge)
275 283 } else if left.len() < right.len() / 2 {
276 284 // Same as above but with `left` and `right` swapped
277 285 ordmap_union_with_merge_by_iter(right, left, |key, a, b| {
278 286 // Also swapped in `merge` arguments:
279 287 match merge(key, b, a) {
280 288 MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v),
281 289 // … and swap back in `merge` result:
282 290 MergeResult::UseLeftValue => MergeResult::UseRightValue,
283 291 MergeResult::UseRightValue => MergeResult::UseLeftValue,
284 292 }
285 293 })
286 294 } else {
287 295 // For maps of similar size, use the algorithm based on `OrdMap::diff`
288 296 ordmap_union_with_merge_by_diff(left, right, merge)
289 297 }
290 298 }
291 299
292 300 /// Efficient if `right` is much smaller than `left`
293 301 fn ordmap_union_with_merge_by_iter<K, V>(
294 302 mut left: OrdMap<K, V>,
295 303 right: OrdMap<K, V>,
296 304 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
297 305 ) -> OrdMap<K, V>
298 306 where
299 307 K: Clone + Ord,
300 308 V: Clone,
301 309 {
302 310 for (key, right_value) in right {
303 311 match left.get(&key) {
304 312 None => {
305 313 left.insert(key, right_value);
306 314 }
307 315 Some(left_value) => match merge(&key, left_value, &right_value) {
308 316 MergeResult::UseLeftValue => {}
309 317 MergeResult::UseRightValue => {
310 318 left.insert(key, right_value);
311 319 }
312 320 MergeResult::UseNewValue(new_value) => {
313 321 left.insert(key, new_value);
314 322 }
315 323 },
316 324 }
317 325 }
318 326 left
319 327 }
320 328
321 329 /// Fallback when both maps are of similar size
322 330 fn ordmap_union_with_merge_by_diff<K, V>(
323 331 mut left: OrdMap<K, V>,
324 332 mut right: OrdMap<K, V>,
325 333 mut merge: impl FnMut(&K, &V, &V) -> MergeResult<V>,
326 334 ) -> OrdMap<K, V>
327 335 where
328 336 K: Clone + Ord,
329 337 V: Clone + PartialEq,
330 338 {
331 339 // (key, value) pairs that would need to be inserted in either map
332 340 // in order to turn it into the union.
333 341 //
334 342 // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted,
335 343 // change these from `Vec<(K, V)>` to `Vec<(&K, Cow<V>)>`
336 344 // with `left_updates` only borrowing from `right` and `right_updates` from
337 345 // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`.
338 346 //
339 347 // This would allow moving all `.clone()` calls to after we’ve decided
340 348 // which of `right_updates` or `left_updates` to use
341 349 // (value ones becoming `Cow::into_owned`),
342 350 // and avoid making clones we don’t end up using.
343 351 let mut left_updates = Vec::new();
344 352 let mut right_updates = Vec::new();
345 353
346 354 for difference in left.diff(&right) {
347 355 match difference {
348 356 DiffItem::Add(key, value) => {
349 357 left_updates.push((key.clone(), value.clone()))
350 358 }
351 359 DiffItem::Remove(key, value) => {
352 360 right_updates.push((key.clone(), value.clone()))
353 361 }
354 362 DiffItem::Update {
355 363 old: (key, left_value),
356 364 new: (_, right_value),
357 365 } => match merge(key, left_value, right_value) {
358 366 MergeResult::UseLeftValue => {
359 367 right_updates.push((key.clone(), left_value.clone()))
360 368 }
361 369 MergeResult::UseRightValue => {
362 370 left_updates.push((key.clone(), right_value.clone()))
363 371 }
364 372 MergeResult::UseNewValue(new_value) => {
365 373 left_updates.push((key.clone(), new_value.clone()));
366 374 right_updates.push((key.clone(), new_value))
367 375 }
368 376 },
369 377 }
370 378 }
371 379 if left_updates.len() < right_updates.len() {
372 380 for (key, value) in left_updates {
373 381 left.insert(key, value);
374 382 }
375 383 left
376 384 } else {
377 385 for (key, value) in right_updates {
378 386 right.insert(key, value);
379 387 }
380 388 right
381 389 }
382 390 }
383 391
384 392 /// Join items of the iterable with the given separator, similar to Python’s
385 393 /// `separator.join(iter)`.
386 394 ///
387 395 /// Formatting the return value consumes the iterator.
388 396 /// Formatting it again will produce an empty string.
389 397 pub fn join_display(
390 398 iter: impl IntoIterator<Item = impl fmt::Display>,
391 399 separator: impl fmt::Display,
392 400 ) -> impl fmt::Display {
393 401 JoinDisplay {
394 402 iter: Cell::new(Some(iter.into_iter())),
395 403 separator,
396 404 }
397 405 }
398 406
399 407 struct JoinDisplay<I, S> {
400 408 iter: Cell<Option<I>>,
401 409 separator: S,
402 410 }
403 411
404 412 impl<I, T, S> fmt::Display for JoinDisplay<I, S>
405 413 where
406 414 I: Iterator<Item = T>,
407 415 T: fmt::Display,
408 416 S: fmt::Display,
409 417 {
410 418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 419 if let Some(mut iter) = self.iter.take() {
412 420 if let Some(first) = iter.next() {
413 421 first.fmt(f)?;
414 422 }
415 423 for value in iter {
416 424 self.separator.fmt(f)?;
417 425 value.fmt(f)?;
418 426 }
419 427 }
420 428 Ok(())
421 429 }
422 430 }
General Comments 0
You need to be logged in to leave comments. Login now