rustdoc/html/url_parts_builder.rs
1use std::fmt::{self, Write};
2
3use rustc_span::Symbol;
4
5/// A builder that allows efficiently and easily constructing the part of a URL
6/// after the domain: `nightly/core/str/struct.Bytes.html`.
7///
8/// This type is a wrapper around the final `String` buffer,
9/// but its API is like that of a `Vec` of URL components.
10#[derive(Debug)]
11pub(crate) struct UrlPartsBuilder {
12 buf: String,
13}
14
15impl UrlPartsBuilder {
16 /// Create an empty buffer.
17 pub(crate) fn new() -> Self {
18 Self { buf: String::new() }
19 }
20
21 /// Create an empty buffer with capacity for the specified number of bytes.
22 fn with_capacity_bytes(count: usize) -> Self {
23 Self { buf: String::with_capacity(count) }
24 }
25
26 /// Create a buffer with one URL component.
27 ///
28 /// # Examples
29 ///
30 /// Basic usage:
31 ///
32 /// ```ignore (private-type)
33 /// let builder = UrlPartsBuilder::singleton("core");
34 /// assert_eq!(builder.finish(), "core");
35 /// ```
36 ///
37 /// Adding more components afterward.
38 ///
39 /// ```ignore (private-type)
40 /// let mut builder = UrlPartsBuilder::singleton("core");
41 /// builder.push("str");
42 /// builder.push_front("nightly");
43 /// assert_eq!(builder.finish(), "nightly/core/str");
44 /// ```
45 pub(crate) fn singleton(part: &str) -> Self {
46 Self { buf: part.to_owned() }
47 }
48
49 /// Push a component onto the buffer.
50 ///
51 /// # Examples
52 ///
53 /// Basic usage:
54 ///
55 /// ```ignore (private-type)
56 /// let mut builder = UrlPartsBuilder::new();
57 /// builder.push("core");
58 /// builder.push("str");
59 /// builder.push("struct.Bytes.html");
60 /// assert_eq!(builder.finish(), "core/str/struct.Bytes.html");
61 /// ```
62 pub(crate) fn push(&mut self, part: &str) {
63 if !self.buf.is_empty() {
64 self.buf.push('/');
65 }
66 self.buf.push_str(part);
67 }
68
69 /// Push a component onto the buffer, using [`format!`]'s formatting syntax.
70 ///
71 /// # Examples
72 ///
73 /// Basic usage (equivalent to the example for [`UrlPartsBuilder::push`]):
74 ///
75 /// ```ignore (private-type)
76 /// let mut builder = UrlPartsBuilder::new();
77 /// builder.push("core");
78 /// builder.push("str");
79 /// builder.push_fmt(format_args!("{}.{}.html", "struct", "Bytes"));
80 /// assert_eq!(builder.finish(), "core/str/struct.Bytes.html");
81 /// ```
82 pub(crate) fn push_fmt(&mut self, args: fmt::Arguments<'_>) {
83 if !self.buf.is_empty() {
84 self.buf.push('/');
85 }
86 self.buf.write_fmt(args).unwrap()
87 }
88
89 /// Push a component onto the front of the buffer.
90 ///
91 /// # Examples
92 ///
93 /// Basic usage:
94 ///
95 /// ```ignore (private-type)
96 /// let mut builder = UrlPartsBuilder::new();
97 /// builder.push("core");
98 /// builder.push("str");
99 /// builder.push_front("nightly");
100 /// builder.push("struct.Bytes.html");
101 /// assert_eq!(builder.finish(), "nightly/core/str/struct.Bytes.html");
102 /// ```
103 pub(crate) fn push_front(&mut self, part: &str) {
104 let is_empty = self.buf.is_empty();
105 self.buf.reserve(part.len() + if !is_empty { 1 } else { 0 });
106 self.buf.insert_str(0, part);
107 if !is_empty {
108 self.buf.insert(part.len(), '/');
109 }
110 }
111
112 /// Get the final `String` buffer.
113 pub(crate) fn finish(self) -> String {
114 self.buf
115 }
116}
117
118/// This is just a guess at the average length of a URL part,
119/// used for [`String::with_capacity`] calls in the [`FromIterator`]
120/// and [`Extend`] impls.
121///
122/// The value `8` was chosen for two main reasons:
123///
124/// * It seems like a good guess for the average part length.
125/// * jemalloc's size classes are all multiples of eight,
126/// which means that the amount of memory it allocates will often match
127/// the amount requested, avoiding wasted bytes.
128const AVG_PART_LENGTH: usize = 8;
129
130impl<'a> FromIterator<&'a str> for UrlPartsBuilder {
131 fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
132 let iter = iter.into_iter();
133 let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0);
134 iter.for_each(|part| builder.push(part));
135 builder
136 }
137}
138
139impl<'a> Extend<&'a str> for UrlPartsBuilder {
140 fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
141 let iter = iter.into_iter();
142 self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0);
143 iter.for_each(|part| self.push(part));
144 }
145}
146
147impl FromIterator<Symbol> for UrlPartsBuilder {
148 fn from_iter<T: IntoIterator<Item = Symbol>>(iter: T) -> Self {
149 // This code has to be duplicated from the `&str` impl because of
150 // `Symbol::as_str`'s lifetimes.
151 let iter = iter.into_iter();
152 let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0);
153 iter.for_each(|part| builder.push(part.as_str()));
154 builder
155 }
156}
157
158impl Extend<Symbol> for UrlPartsBuilder {
159 fn extend<T: IntoIterator<Item = Symbol>>(&mut self, iter: T) {
160 // This code has to be duplicated from the `&str` impl because of
161 // `Symbol::as_str`'s lifetimes.
162 let iter = iter.into_iter();
163 self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0);
164 iter.for_each(|part| self.push(part.as_str()));
165 }
166}
167
168#[cfg(test)]
169mod tests;