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