]> git.proxmox.com Git - rustc.git/blame - vendor/url-1.7.2/src/path_segments.rs
New upstream version 1.39.0+dfsg1
[rustc.git] / vendor / url-1.7.2 / src / path_segments.rs
CommitLineData
abe05a73
XL
1// Copyright 2016 The rust-url developers.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use parser::{self, SchemeType, to_u32};
10use std::str;
11use Url;
12
13/// Exposes methods to manipulate the path of an URL that is not cannot-be-base.
14///
15/// The path always starts with a `/` slash, and is made of slash-separated segments.
16/// There is always at least one segment (which may be the empty string).
17///
18/// Examples:
19///
20/// ```rust
21/// use url::Url;
22/// # use std::error::Error;
23///
24/// # fn run() -> Result<(), Box<Error>> {
25/// let mut url = Url::parse("mailto:me@example.com")?;
26/// assert!(url.path_segments_mut().is_err());
27///
28/// let mut url = Url::parse("http://example.net/foo/index.html")?;
29/// url.path_segments_mut().map_err(|_| "cannot be base")?
30/// .pop().push("img").push("2/100%.png");
31/// assert_eq!(url.as_str(), "http://example.net/foo/img/2%2F100%25.png");
32/// # Ok(())
33/// # }
34/// # run().unwrap();
35/// ```
36#[derive(Debug)]
37pub struct PathSegmentsMut<'a> {
38 url: &'a mut Url,
39 after_first_slash: usize,
40 after_path: String,
41 old_after_path_position: u32,
42}
43
44// Not re-exported outside the crate
45pub fn new(url: &mut Url) -> PathSegmentsMut {
46 let after_path = url.take_after_path();
47 let old_after_path_position = to_u32(url.serialization.len()).unwrap();
48 debug_assert!(url.byte_at(url.path_start) == b'/');
49 PathSegmentsMut {
50 after_first_slash: url.path_start as usize + "/".len(),
51 url: url,
52 old_after_path_position: old_after_path_position,
53 after_path: after_path,
54 }
55}
56
57impl<'a> Drop for PathSegmentsMut<'a> {
58 fn drop(&mut self) {
59 self.url.restore_after_path(self.old_after_path_position, &self.after_path)
60 }
61}
62
63impl<'a> PathSegmentsMut<'a> {
64 /// Remove all segments in the path, leaving the minimal `url.path() == "/"`.
65 ///
66 /// Returns `&mut Self` so that method calls can be chained.
67 ///
68 /// Example:
69 ///
70 /// ```rust
71 /// use url::Url;
72 /// # use std::error::Error;
73 ///
74 /// # fn run() -> Result<(), Box<Error>> {
75 /// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
76 /// url.path_segments_mut().map_err(|_| "cannot be base")?
77 /// .clear().push("logout");
78 /// assert_eq!(url.as_str(), "https://github.com/logout");
79 /// # Ok(())
80 /// # }
81 /// # run().unwrap();
82 /// ```
83 pub fn clear(&mut self) -> &mut Self {
84 self.url.serialization.truncate(self.after_first_slash);
85 self
86 }
87
88 /// Remove the last segment of this URL’s path if it is empty,
89 /// except if these was only one segment to begin with.
90 ///
91 /// In other words, remove one path trailing slash, if any,
92 /// unless it is also the initial slash (so this does nothing if `url.path() == "/")`.
93 ///
94 /// Returns `&mut Self` so that method calls can be chained.
95 ///
96 /// Example:
97 ///
98 /// ```rust
99 /// use url::Url;
100 /// # use std::error::Error;
101 ///
102 /// # fn run() -> Result<(), Box<Error>> {
103 /// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
104 /// url.path_segments_mut().map_err(|_| "cannot be base")?
105 /// .push("pulls");
106 /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url//pulls");
107 ///
108 /// let mut url = Url::parse("https://github.com/servo/rust-url/")?;
109 /// url.path_segments_mut().map_err(|_| "cannot be base")?
110 /// .pop_if_empty().push("pulls");
111 /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
112 /// # Ok(())
113 /// # }
114 /// # run().unwrap();
115 /// ```
116 pub fn pop_if_empty(&mut self) -> &mut Self {
117 if self.url.serialization[self.after_first_slash..].ends_with('/') {
118 self.url.serialization.pop();
119 }
120 self
121 }
122
123 /// Remove the last segment of this URL’s path.
124 ///
125 /// If the path only has one segment, make it empty such that `url.path() == "/"`.
126 ///
127 /// Returns `&mut Self` so that method calls can be chained.
128 pub fn pop(&mut self) -> &mut Self {
129 let last_slash = self.url.serialization[self.after_first_slash..].rfind('/').unwrap_or(0);
130 self.url.serialization.truncate(self.after_first_slash + last_slash);
131 self
132 }
133
134 /// Append the given segment at the end of this URL’s path.
135 ///
136 /// See the documentation for `.extend()`.
137 ///
138 /// Returns `&mut Self` so that method calls can be chained.
139 pub fn push(&mut self, segment: &str) -> &mut Self {
140 self.extend(Some(segment))
141 }
142
143 /// Append each segment from the given iterator at the end of this URL’s path.
144 ///
145 /// Each segment is percent-encoded like in `Url::parse` or `Url::join`,
146 /// except that `%` and `/` characters are also encoded (to `%25` and `%2F`).
147 /// This is unlike `Url::parse` where `%` is left as-is in case some of the input
148 /// is already percent-encoded, and `/` denotes a path segment separator.)
149 ///
150 /// Note that, in addition to slashes between new segments,
151 /// this always adds a slash between the existing path and the new segments
152 /// *except* if the existing path is `"/"`.
153 /// If the previous last segment was empty (if the path had a trailing slash)
154 /// the path after `.extend()` will contain two consecutive slashes.
155 /// If that is undesired, call `.pop_if_empty()` first.
156 ///
157 /// To obtain a behavior similar to `Url::join`, call `.pop()` unconditionally first.
158 ///
159 /// Returns `&mut Self` so that method calls can be chained.
160 ///
161 /// Example:
162 ///
163 /// ```rust
164 /// use url::Url;
165 /// # use std::error::Error;
166 ///
167 /// # fn run() -> Result<(), Box<Error>> {
168 /// let mut url = Url::parse("https://github.com/")?;
169 /// let org = "servo";
170 /// let repo = "rust-url";
171 /// let issue_number = "188";
172 /// url.path_segments_mut().map_err(|_| "cannot be base")?
173 /// .extend(&[org, repo, "issues", issue_number]);
174 /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/issues/188");
175 /// # Ok(())
176 /// # }
177 /// # run().unwrap();
178 /// ```
179 ///
180 /// In order to make sure that parsing the serialization of an URL gives the same URL,
181 /// a segment is ignored if it is `"."` or `".."`:
182 ///
183 /// ```rust
184 /// use url::Url;
185 /// # use std::error::Error;
186 ///
187 /// # fn run() -> Result<(), Box<Error>> {
188 /// let mut url = Url::parse("https://github.com/servo")?;
189 /// url.path_segments_mut().map_err(|_| "cannot be base")?
190 /// .extend(&["..", "rust-url", ".", "pulls"]);
191 /// assert_eq!(url.as_str(), "https://github.com/servo/rust-url/pulls");
192 /// # Ok(())
193 /// # }
194 /// # run().unwrap();
195 /// ```
196 pub fn extend<I>(&mut self, segments: I) -> &mut Self
197 where I: IntoIterator, I::Item: AsRef<str> {
198 let scheme_type = SchemeType::from(self.url.scheme());
199 let path_start = self.url.path_start as usize;
200 self.url.mutate(|parser| {
201 parser.context = parser::Context::PathSegmentSetter;
202 for segment in segments {
203 let segment = segment.as_ref();
204 if matches!(segment, "." | "..") {
205 continue
206 }
207 if parser.serialization.len() > path_start + 1 {
208 parser.serialization.push('/');
209 }
210 let mut has_host = true; // FIXME account for this?
211 parser.parse_path(scheme_type, &mut has_host, path_start,
212 parser::Input::new(segment));
213 }
214 });
215 self
216 }
217}