diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index a3cbb5756fe..f6788e94431 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -27,6 +27,8 @@
use crate::html::render::cache::ExternalLocation;
use crate::html::render::Context;
+use super::url_parts_builder::UrlPartsBuilder;
+
crate trait Print {
fn print(self, buffer: &mut Buffer);
}
@@ -544,9 +546,9 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
ExternalLocation::Remote(ref s) => {
is_remote = true;
let s = s.trim_end_matches('/');
- let mut s = vec![s];
- s.extend(module_fqp[..].iter().map(String::as_str));
- s
+ let mut builder = UrlPartsBuilder::singleton(s);
+ builder.extend(module_fqp.iter().map(String::as_str));
+ builder
}
ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
@@ -560,22 +562,21 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
if !is_remote {
if let Some(root_path) = root_path {
let root = root_path.trim_end_matches('/');
- url_parts.insert(0, root);
+ url_parts.push_front(root);
}
}
debug!(?url_parts);
let last = &fqp.last().unwrap()[..];
- let filename;
match shortty {
ItemType::Module => {
url_parts.push("index.html");
}
_ => {
- filename = format!("{}.{}.html", shortty.as_str(), last);
+ let filename = format!("{}.{}.html", shortty.as_str(), last);
url_parts.push(&filename);
}
}
- Ok((url_parts.join("/"), shortty, fqp.to_vec()))
+ Ok((url_parts.finish(), shortty, fqp.to_vec()))
}
crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec), HrefError> {
@@ -585,7 +586,7 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
/// Both paths should only be modules.
/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
/// both need `../iter/trait.Iterator.html` to get at the iterator trait.
-crate fn href_relative_parts<'a>(fqp: &'a [String], relative_to_fqp: &'a [String]) -> Vec<&'a str> {
+crate fn href_relative_parts(fqp: &[String], relative_to_fqp: &[String]) -> UrlPartsBuilder {
for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
// e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
if f != r {
@@ -603,7 +604,7 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
iter::repeat("..").take(dissimilar_part_count).collect()
// linking to the same module
} else {
- Vec::new()
+ UrlPartsBuilder::new()
}
}
diff --git a/src/librustdoc/html/mod.rs b/src/librustdoc/html/mod.rs
index 109b0a356db..e1bbc784fd1 100644
--- a/src/librustdoc/html/mod.rs
+++ b/src/librustdoc/html/mod.rs
@@ -9,6 +9,7 @@
crate mod sources;
crate mod static_files;
crate mod toc;
+mod url_parts_builder;
#[cfg(test)]
mod tests;
diff --git a/src/librustdoc/html/tests.rs b/src/librustdoc/html/tests.rs
index 5d537dabd0c..dee9f5e5038 100644
--- a/src/librustdoc/html/tests.rs
+++ b/src/librustdoc/html/tests.rs
@@ -1,44 +1,44 @@
use crate::html::format::href_relative_parts;
-fn assert_relative_path(expected: &[&str], relative_to_fqp: &[&str], fqp: &[&str]) {
+fn assert_relative_path(expected: &str, relative_to_fqp: &[&str], fqp: &[&str]) {
let relative_to_fqp: Vec = relative_to_fqp.iter().copied().map(String::from).collect();
let fqp: Vec = fqp.iter().copied().map(String::from).collect();
- assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp));
+ assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).finish());
}
#[test]
fn href_relative_parts_basic() {
let relative_to_fqp = &["std", "vec"];
let fqp = &["std", "iter"];
- assert_relative_path(&["..", "iter"], relative_to_fqp, fqp);
+ assert_relative_path("../iter", relative_to_fqp, fqp);
}
#[test]
fn href_relative_parts_parent_module() {
let relative_to_fqp = &["std", "vec"];
let fqp = &["std"];
- assert_relative_path(&[".."], relative_to_fqp, fqp);
+ assert_relative_path("..", relative_to_fqp, fqp);
}
#[test]
fn href_relative_parts_different_crate() {
let relative_to_fqp = &["std", "vec"];
let fqp = &["core", "iter"];
- assert_relative_path(&["..", "..", "core", "iter"], relative_to_fqp, fqp);
+ assert_relative_path("../../core/iter", relative_to_fqp, fqp);
}
#[test]
fn href_relative_parts_same_module() {
let relative_to_fqp = &["std", "vec"];
let fqp = &["std", "vec"];
- assert_relative_path(&[], relative_to_fqp, fqp);
+ assert_relative_path("", relative_to_fqp, fqp);
}
#[test]
fn href_relative_parts_child_module() {
let relative_to_fqp = &["std"];
let fqp = &["std", "vec"];
- assert_relative_path(&["vec"], relative_to_fqp, fqp);
+ assert_relative_path("vec", relative_to_fqp, fqp);
}
#[test]
fn href_relative_parts_root() {
let relative_to_fqp = &[];
let fqp = &["std"];
- assert_relative_path(&["std"], relative_to_fqp, fqp);
+ assert_relative_path("std", relative_to_fqp, fqp);
}
diff --git a/src/librustdoc/html/url_parts_builder.rs b/src/librustdoc/html/url_parts_builder.rs
new file mode 100644
index 00000000000..918d5e6bd1b
--- /dev/null
+++ b/src/librustdoc/html/url_parts_builder.rs
@@ -0,0 +1,119 @@
+/// A builder that allows efficiently and easily constructing the part of a URL
+/// after the domain: `nightly/core/str/struct.Bytes.html`.
+///
+/// This type is a wrapper around the final `String` buffer,
+/// but its API is like that of a `Vec` of URL components.
+#[derive(Debug)]
+crate struct UrlPartsBuilder {
+ buf: String,
+}
+
+impl UrlPartsBuilder {
+ /// Create an empty buffer.
+ crate fn new() -> Self {
+ Self { buf: String::new() }
+ }
+
+ /// Create an empty buffer with capacity for the specified number of bytes.
+ fn with_capacity_bytes(count: usize) -> Self {
+ Self { buf: String::with_capacity(count) }
+ }
+
+ /// Create a buffer with one URL component.
+ ///
+ /// # Examples
+ ///
+ /// Basic usage:
+ ///
+ /// ```ignore (private-type)
+ /// let builder = UrlPartsBuilder::singleton("core");
+ /// assert_eq!(builder.finish(), "core");
+ /// ```
+ ///
+ /// Adding more components afterward.
+ ///
+ /// ```ignore (private-type)
+ /// let mut builder = UrlPartsBuilder::singleton("core");
+ /// builder.push("str");
+ /// builder.push_front("nightly");
+ /// assert_eq!(builder.finish(), "nightly/core/str");
+ /// ```
+ crate fn singleton(part: &str) -> Self {
+ Self { buf: part.to_owned() }
+ }
+
+ /// Push a component onto the buffer.
+ ///
+ /// # Examples
+ ///
+ /// Basic usage:
+ ///
+ /// ```ignore (private-type)
+ /// let mut builder = UrlPartsBuilder::new();
+ /// builder.push("core");
+ /// builder.push("str");
+ /// builder.push("struct.Bytes.html");
+ /// assert_eq!(builder.finish(), "core/str/struct.Bytes.html");
+ /// ```
+ crate fn push(&mut self, part: &str) {
+ if !self.buf.is_empty() {
+ self.buf.push('/');
+ }
+ self.buf.push_str(part);
+ }
+
+ /// Push a component onto the front of the buffer.
+ ///
+ /// # Examples
+ ///
+ /// Basic usage:
+ ///
+ /// ```ignore (private-type)
+ /// let mut builder = UrlPartsBuilder::new();
+ /// builder.push("core");
+ /// builder.push("str");
+ /// builder.push_front("nightly");
+ /// builder.push("struct.Bytes.html");
+ /// assert_eq!(builder.finish(), "nightly/core/str/struct.Bytes.html");
+ /// ```
+ crate fn push_front(&mut self, part: &str) {
+ let is_empty = self.buf.is_empty();
+ self.buf.reserve(part.len() + if !is_empty { 1 } else { 0 });
+ self.buf.insert_str(0, part);
+ if !is_empty {
+ self.buf.insert(part.len(), '/');
+ }
+ }
+
+ /// Get the final `String` buffer.
+ crate fn finish(self) -> String {
+ self.buf
+ }
+}
+
+/// This is just a guess at the average length of a URL part,
+/// used for [`String::with_capacity`] calls in the [`FromIterator`]
+/// and [`Extend`] impls.
+///
+/// This is intentionally on the lower end to avoid overallocating.
+const AVG_PART_LENGTH: usize = 5;
+
+impl<'a> FromIterator<&'a str> for UrlPartsBuilder {
+ fn from_iter>(iter: T) -> Self {
+ let iter = iter.into_iter();
+ let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0);
+ iter.for_each(|part| builder.push(part));
+ builder
+ }
+}
+
+impl<'a> Extend<&'a str> for UrlPartsBuilder {
+ fn extend>(&mut self, iter: T) {
+ let iter = iter.into_iter();
+ self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0);
+ iter.for_each(|part| self.push(part));
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/librustdoc/html/url_parts_builder/tests.rs b/src/librustdoc/html/url_parts_builder/tests.rs
new file mode 100644
index 00000000000..43338c95010
--- /dev/null
+++ b/src/librustdoc/html/url_parts_builder/tests.rs
@@ -0,0 +1,54 @@
+use super::*;
+
+fn t(builder: UrlPartsBuilder, expect: &str) {
+ assert_eq!(builder.finish(), expect);
+}
+
+#[test]
+fn empty() {
+ t(UrlPartsBuilder::new(), "");
+}
+
+#[test]
+fn singleton() {
+ t(UrlPartsBuilder::singleton("index.html"), "index.html");
+}
+
+#[test]
+fn push_several() {
+ let mut builder = UrlPartsBuilder::new();
+ builder.push("core");
+ builder.push("str");
+ builder.push("struct.Bytes.html");
+ t(builder, "core/str/struct.Bytes.html");
+}
+
+#[test]
+fn push_front_empty() {
+ let mut builder = UrlPartsBuilder::new();
+ builder.push_front("page.html");
+ t(builder, "page.html");
+}
+
+#[test]
+fn push_front_non_empty() {
+ let mut builder = UrlPartsBuilder::new();
+ builder.push("core");
+ builder.push("str");
+ builder.push("struct.Bytes.html");
+ builder.push_front("nightly");
+ t(builder, "nightly/core/str/struct.Bytes.html");
+}
+
+#[test]
+fn collect() {
+ t(["core", "str"].into_iter().collect(), "core/str");
+ t(["core", "str", "struct.Bytes.html"].into_iter().collect(), "core/str/struct.Bytes.html");
+}
+
+#[test]
+fn extend() {
+ let mut builder = UrlPartsBuilder::singleton("core");
+ builder.extend(["str", "struct.Bytes.html"]);
+ t(builder, "core/str/struct.Bytes.html");
+}