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::cache::ExternalLocation;
|
||||||
use crate::html::render::Context;
|
use crate::html::render::Context;
|
||||||
|
|
||||||
|
use super::url_parts_builder::UrlPartsBuilder;
|
||||||
|
|
||||||
crate trait Print {
|
crate trait Print {
|
||||||
fn print(self, buffer: &mut Buffer);
|
fn print(self, buffer: &mut Buffer);
|
||||||
}
|
}
|
||||||
@ -544,9 +546,9 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
|
|||||||
ExternalLocation::Remote(ref s) => {
|
ExternalLocation::Remote(ref s) => {
|
||||||
is_remote = true;
|
is_remote = true;
|
||||||
let s = s.trim_end_matches('/');
|
let s = s.trim_end_matches('/');
|
||||||
let mut s = vec![s];
|
let mut builder = UrlPartsBuilder::singleton(s);
|
||||||
s.extend(module_fqp[..].iter().map(String::as_str));
|
builder.extend(module_fqp.iter().map(String::as_str));
|
||||||
s
|
builder
|
||||||
}
|
}
|
||||||
ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
|
ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
|
||||||
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
|
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
|
||||||
@ -560,22 +562,21 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
|
|||||||
if !is_remote {
|
if !is_remote {
|
||||||
if let Some(root_path) = root_path {
|
if let Some(root_path) = root_path {
|
||||||
let root = root_path.trim_end_matches('/');
|
let root = root_path.trim_end_matches('/');
|
||||||
url_parts.insert(0, root);
|
url_parts.push_front(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!(?url_parts);
|
debug!(?url_parts);
|
||||||
let last = &fqp.last().unwrap()[..];
|
let last = &fqp.last().unwrap()[..];
|
||||||
let filename;
|
|
||||||
match shortty {
|
match shortty {
|
||||||
ItemType::Module => {
|
ItemType::Module => {
|
||||||
url_parts.push("index.html");
|
url_parts.push("index.html");
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
filename = format!("{}.{}.html", shortty.as_str(), last);
|
let filename = format!("{}.{}.html", shortty.as_str(), last);
|
||||||
url_parts.push(&filename);
|
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> {
|
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.
|
/// Both paths should only be modules.
|
||||||
/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
|
/// 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.
|
/// 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() {
|
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)
|
// e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
|
||||||
if f != r {
|
if f != r {
|
||||||
@ -603,7 +604,7 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
|
|||||||
iter::repeat("..").take(dissimilar_part_count).collect()
|
iter::repeat("..").take(dissimilar_part_count).collect()
|
||||||
// linking to the same module
|
// linking to the same module
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
UrlPartsBuilder::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
crate mod sources;
|
crate mod sources;
|
||||||
crate mod static_files;
|
crate mod static_files;
|
||||||
crate mod toc;
|
crate mod toc;
|
||||||
|
mod url_parts_builder;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -1,44 +1,44 @@
|
|||||||
use crate::html::format::href_relative_parts;
|
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 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();
|
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]
|
#[test]
|
||||||
fn href_relative_parts_basic() {
|
fn href_relative_parts_basic() {
|
||||||
let relative_to_fqp = &["std", "vec"];
|
let relative_to_fqp = &["std", "vec"];
|
||||||
let fqp = &["std", "iter"];
|
let fqp = &["std", "iter"];
|
||||||
assert_relative_path(&["..", "iter"], relative_to_fqp, fqp);
|
assert_relative_path("../iter", relative_to_fqp, fqp);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn href_relative_parts_parent_module() {
|
fn href_relative_parts_parent_module() {
|
||||||
let relative_to_fqp = &["std", "vec"];
|
let relative_to_fqp = &["std", "vec"];
|
||||||
let fqp = &["std"];
|
let fqp = &["std"];
|
||||||
assert_relative_path(&[".."], relative_to_fqp, fqp);
|
assert_relative_path("..", relative_to_fqp, fqp);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn href_relative_parts_different_crate() {
|
fn href_relative_parts_different_crate() {
|
||||||
let relative_to_fqp = &["std", "vec"];
|
let relative_to_fqp = &["std", "vec"];
|
||||||
let fqp = &["core", "iter"];
|
let fqp = &["core", "iter"];
|
||||||
assert_relative_path(&["..", "..", "core", "iter"], relative_to_fqp, fqp);
|
assert_relative_path("../../core/iter", relative_to_fqp, fqp);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn href_relative_parts_same_module() {
|
fn href_relative_parts_same_module() {
|
||||||
let relative_to_fqp = &["std", "vec"];
|
let relative_to_fqp = &["std", "vec"];
|
||||||
let fqp = &["std", "vec"];
|
let fqp = &["std", "vec"];
|
||||||
assert_relative_path(&[], relative_to_fqp, fqp);
|
assert_relative_path("", relative_to_fqp, fqp);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn href_relative_parts_child_module() {
|
fn href_relative_parts_child_module() {
|
||||||
let relative_to_fqp = &["std"];
|
let relative_to_fqp = &["std"];
|
||||||
let fqp = &["std", "vec"];
|
let fqp = &["std", "vec"];
|
||||||
assert_relative_path(&["vec"], relative_to_fqp, fqp);
|
assert_relative_path("vec", relative_to_fqp, fqp);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn href_relative_parts_root() {
|
fn href_relative_parts_root() {
|
||||||
let relative_to_fqp = &[];
|
let relative_to_fqp = &[];
|
||||||
let fqp = &["std"];
|
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