rustdoc: Add UrlPartsBuilder
This is a type for efficiently and easily constructing the part of a URL after the domain: `nightly/core/str/struct.Bytes.html`. It allows simplifying some code and avoiding some allocations in the `href_*` functions. It will also allow making `Cache.paths` et al. use `Symbol` without having to allocate `String`s in the `href_*` functions. `String`s would be necessary otherwise because `Symbol::as_str()` returns `SymbolStr`, whose `Deref<Target = str>` impl requires the `str` to not outlive it. This is the primary motivation for the addition of `UrlPartsBuilder`.
This commit is contained in:
parent
1796de7bb1
commit
4bac09f58f
@ -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<String>), 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
crate mod sources;
|
||||
crate mod static_files;
|
||||
crate mod toc;
|
||||
mod url_parts_builder;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -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<String> = relative_to_fqp.iter().copied().map(String::from).collect();
|
||||
let fqp: Vec<String> = 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);
|
||||
}
|
||||
|
119
src/librustdoc/html/url_parts_builder.rs
Normal file
119
src/librustdoc/html/url_parts_builder.rs
Normal file
@ -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<T: IntoIterator<Item = &'a str>>(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<T: IntoIterator<Item = &'a str>>(&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;
|
54
src/librustdoc/html/url_parts_builder/tests.rs
Normal file
54
src/librustdoc/html/url_parts_builder/tests.rs
Normal file
@ -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");
|
||||
}
|
Loading…
Reference in New Issue
Block a user