Skip to content

Commit 8f54061

Browse files
committed
Auto merge of #91871 - camelid:urlpartsbuilder, r=GuillaumeGomez
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`.
2 parents d6cffe4 + 4bac09f commit 8f54061

File tree

5 files changed

+192
-17
lines changed

5 files changed

+192
-17
lines changed

src/librustdoc/html/format.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ use crate::html::escape::Escape;
2727
use crate::html::render::cache::ExternalLocation;
2828
use crate::html::render::Context;
2929

30+
use super::url_parts_builder::UrlPartsBuilder;
31+
3032
crate trait Print {
3133
fn print(self, buffer: &mut Buffer);
3234
}
@@ -544,9 +546,9 @@ crate fn href_with_root_path(
544546
ExternalLocation::Remote(ref s) => {
545547
is_remote = true;
546548
let s = s.trim_end_matches('/');
547-
let mut s = vec![s];
548-
s.extend(module_fqp[..].iter().map(String::as_str));
549-
s
549+
let mut builder = UrlPartsBuilder::singleton(s);
550+
builder.extend(module_fqp.iter().map(String::as_str));
551+
builder
550552
}
551553
ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
552554
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
@@ -560,22 +562,21 @@ crate fn href_with_root_path(
560562
if !is_remote {
561563
if let Some(root_path) = root_path {
562564
let root = root_path.trim_end_matches('/');
563-
url_parts.insert(0, root);
565+
url_parts.push_front(root);
564566
}
565567
}
566568
debug!(?url_parts);
567569
let last = &fqp.last().unwrap()[..];
568-
let filename;
569570
match shortty {
570571
ItemType::Module => {
571572
url_parts.push("index.html");
572573
}
573574
_ => {
574-
filename = format!("{}.{}.html", shortty.as_str(), last);
575+
let filename = format!("{}.{}.html", shortty.as_str(), last);
575576
url_parts.push(&filename);
576577
}
577578
}
578-
Ok((url_parts.join("/"), shortty, fqp.to_vec()))
579+
Ok((url_parts.finish(), shortty, fqp.to_vec()))
579580
}
580581

581582
crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<String>), HrefError> {
@@ -585,7 +586,7 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Str
585586
/// Both paths should only be modules.
586587
/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
587588
/// both need `../iter/trait.Iterator.html` to get at the iterator trait.
588-
crate fn href_relative_parts<'a>(fqp: &'a [String], relative_to_fqp: &'a [String]) -> Vec<&'a str> {
589+
crate fn href_relative_parts(fqp: &[String], relative_to_fqp: &[String]) -> UrlPartsBuilder {
589590
for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
590591
// e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
591592
if f != r {
@@ -603,7 +604,7 @@ crate fn href_relative_parts<'a>(fqp: &'a [String], relative_to_fqp: &'a [String
603604
iter::repeat("..").take(dissimilar_part_count).collect()
604605
// linking to the same module
605606
} else {
606-
Vec::new()
607+
UrlPartsBuilder::new()
607608
}
608609
}
609610

src/librustdoc/html/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ crate mod render;
99
crate mod sources;
1010
crate mod static_files;
1111
crate mod toc;
12+
mod url_parts_builder;
1213

1314
#[cfg(test)]
1415
mod tests;

src/librustdoc/html/tests.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
11
use crate::html::format::href_relative_parts;
22

3-
fn assert_relative_path(expected: &[&str], relative_to_fqp: &[&str], fqp: &[&str]) {
3+
fn assert_relative_path(expected: &str, relative_to_fqp: &[&str], fqp: &[&str]) {
44
let relative_to_fqp: Vec<String> = relative_to_fqp.iter().copied().map(String::from).collect();
55
let fqp: Vec<String> = fqp.iter().copied().map(String::from).collect();
6-
assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp));
6+
assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).finish());
77
}
88

99
#[test]
1010
fn href_relative_parts_basic() {
1111
let relative_to_fqp = &["std", "vec"];
1212
let fqp = &["std", "iter"];
13-
assert_relative_path(&["..", "iter"], relative_to_fqp, fqp);
13+
assert_relative_path("../iter", relative_to_fqp, fqp);
1414
}
1515
#[test]
1616
fn href_relative_parts_parent_module() {
1717
let relative_to_fqp = &["std", "vec"];
1818
let fqp = &["std"];
19-
assert_relative_path(&[".."], relative_to_fqp, fqp);
19+
assert_relative_path("..", relative_to_fqp, fqp);
2020
}
2121
#[test]
2222
fn href_relative_parts_different_crate() {
2323
let relative_to_fqp = &["std", "vec"];
2424
let fqp = &["core", "iter"];
25-
assert_relative_path(&["..", "..", "core", "iter"], relative_to_fqp, fqp);
25+
assert_relative_path("../../core/iter", relative_to_fqp, fqp);
2626
}
2727
#[test]
2828
fn href_relative_parts_same_module() {
2929
let relative_to_fqp = &["std", "vec"];
3030
let fqp = &["std", "vec"];
31-
assert_relative_path(&[], relative_to_fqp, fqp);
31+
assert_relative_path("", relative_to_fqp, fqp);
3232
}
3333
#[test]
3434
fn href_relative_parts_child_module() {
3535
let relative_to_fqp = &["std"];
3636
let fqp = &["std", "vec"];
37-
assert_relative_path(&["vec"], relative_to_fqp, fqp);
37+
assert_relative_path("vec", relative_to_fqp, fqp);
3838
}
3939
#[test]
4040
fn href_relative_parts_root() {
4141
let relative_to_fqp = &[];
4242
let fqp = &["std"];
43-
assert_relative_path(&["std"], relative_to_fqp, fqp);
43+
assert_relative_path("std", relative_to_fqp, fqp);
4444
}
+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/// A builder that allows efficiently and easily constructing the part of a URL
2+
/// after the domain: `nightly/core/str/struct.Bytes.html`.
3+
///
4+
/// This type is a wrapper around the final `String` buffer,
5+
/// but its API is like that of a `Vec` of URL components.
6+
#[derive(Debug)]
7+
crate struct UrlPartsBuilder {
8+
buf: String,
9+
}
10+
11+
impl UrlPartsBuilder {
12+
/// Create an empty buffer.
13+
crate fn new() -> Self {
14+
Self { buf: String::new() }
15+
}
16+
17+
/// Create an empty buffer with capacity for the specified number of bytes.
18+
fn with_capacity_bytes(count: usize) -> Self {
19+
Self { buf: String::with_capacity(count) }
20+
}
21+
22+
/// Create a buffer with one URL component.
23+
///
24+
/// # Examples
25+
///
26+
/// Basic usage:
27+
///
28+
/// ```ignore (private-type)
29+
/// let builder = UrlPartsBuilder::singleton("core");
30+
/// assert_eq!(builder.finish(), "core");
31+
/// ```
32+
///
33+
/// Adding more components afterward.
34+
///
35+
/// ```ignore (private-type)
36+
/// let mut builder = UrlPartsBuilder::singleton("core");
37+
/// builder.push("str");
38+
/// builder.push_front("nightly");
39+
/// assert_eq!(builder.finish(), "nightly/core/str");
40+
/// ```
41+
crate fn singleton(part: &str) -> Self {
42+
Self { buf: part.to_owned() }
43+
}
44+
45+
/// Push a component onto the buffer.
46+
///
47+
/// # Examples
48+
///
49+
/// Basic usage:
50+
///
51+
/// ```ignore (private-type)
52+
/// let mut builder = UrlPartsBuilder::new();
53+
/// builder.push("core");
54+
/// builder.push("str");
55+
/// builder.push("struct.Bytes.html");
56+
/// assert_eq!(builder.finish(), "core/str/struct.Bytes.html");
57+
/// ```
58+
crate fn push(&mut self, part: &str) {
59+
if !self.buf.is_empty() {
60+
self.buf.push('/');
61+
}
62+
self.buf.push_str(part);
63+
}
64+
65+
/// Push a component onto the front of the buffer.
66+
///
67+
/// # Examples
68+
///
69+
/// Basic usage:
70+
///
71+
/// ```ignore (private-type)
72+
/// let mut builder = UrlPartsBuilder::new();
73+
/// builder.push("core");
74+
/// builder.push("str");
75+
/// builder.push_front("nightly");
76+
/// builder.push("struct.Bytes.html");
77+
/// assert_eq!(builder.finish(), "nightly/core/str/struct.Bytes.html");
78+
/// ```
79+
crate fn push_front(&mut self, part: &str) {
80+
let is_empty = self.buf.is_empty();
81+
self.buf.reserve(part.len() + if !is_empty { 1 } else { 0 });
82+
self.buf.insert_str(0, part);
83+
if !is_empty {
84+
self.buf.insert(part.len(), '/');
85+
}
86+
}
87+
88+
/// Get the final `String` buffer.
89+
crate fn finish(self) -> String {
90+
self.buf
91+
}
92+
}
93+
94+
/// This is just a guess at the average length of a URL part,
95+
/// used for [`String::with_capacity`] calls in the [`FromIterator`]
96+
/// and [`Extend`] impls.
97+
///
98+
/// This is intentionally on the lower end to avoid overallocating.
99+
const AVG_PART_LENGTH: usize = 5;
100+
101+
impl<'a> FromIterator<&'a str> for UrlPartsBuilder {
102+
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
103+
let iter = iter.into_iter();
104+
let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0);
105+
iter.for_each(|part| builder.push(part));
106+
builder
107+
}
108+
}
109+
110+
impl<'a> Extend<&'a str> for UrlPartsBuilder {
111+
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
112+
let iter = iter.into_iter();
113+
self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0);
114+
iter.for_each(|part| self.push(part));
115+
}
116+
}
117+
118+
#[cfg(test)]
119+
mod tests;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use super::*;
2+
3+
fn t(builder: UrlPartsBuilder, expect: &str) {
4+
assert_eq!(builder.finish(), expect);
5+
}
6+
7+
#[test]
8+
fn empty() {
9+
t(UrlPartsBuilder::new(), "");
10+
}
11+
12+
#[test]
13+
fn singleton() {
14+
t(UrlPartsBuilder::singleton("index.html"), "index.html");
15+
}
16+
17+
#[test]
18+
fn push_several() {
19+
let mut builder = UrlPartsBuilder::new();
20+
builder.push("core");
21+
builder.push("str");
22+
builder.push("struct.Bytes.html");
23+
t(builder, "core/str/struct.Bytes.html");
24+
}
25+
26+
#[test]
27+
fn push_front_empty() {
28+
let mut builder = UrlPartsBuilder::new();
29+
builder.push_front("page.html");
30+
t(builder, "page.html");
31+
}
32+
33+
#[test]
34+
fn push_front_non_empty() {
35+
let mut builder = UrlPartsBuilder::new();
36+
builder.push("core");
37+
builder.push("str");
38+
builder.push("struct.Bytes.html");
39+
builder.push_front("nightly");
40+
t(builder, "nightly/core/str/struct.Bytes.html");
41+
}
42+
43+
#[test]
44+
fn collect() {
45+
t(["core", "str"].into_iter().collect(), "core/str");
46+
t(["core", "str", "struct.Bytes.html"].into_iter().collect(), "core/str/struct.Bytes.html");
47+
}
48+
49+
#[test]
50+
fn extend() {
51+
let mut builder = UrlPartsBuilder::singleton("core");
52+
builder.extend(["str", "struct.Bytes.html"]);
53+
t(builder, "core/str/struct.Bytes.html");
54+
}

0 commit comments

Comments
 (0)