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, and for [estimating item path lengths].
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.
128///
129/// [estimating item path lengths]: estimate_item_path_byte_length
130const AVG_PART_LENGTH: usize = 8;
131
132/// Estimate the number of bytes in an item's path, based on how many segments it has.
133///
134/// **Note:** This is only to be used with, e.g., [`String::with_capacity()`];
135/// the return value is just a rough estimate.
136pub(crate) const fn estimate_item_path_byte_length(segment_count: usize) -> usize {
137    AVG_PART_LENGTH * segment_count
138}
139
140impl<'a> FromIterator<&'a str> for UrlPartsBuilder {
141    fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
142        let iter = iter.into_iter();
143        let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0);
144        iter.for_each(|part| builder.push(part));
145        builder
146    }
147}
148
149impl<'a> Extend<&'a str> for UrlPartsBuilder {
150    fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
151        let iter = iter.into_iter();
152        self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0);
153        iter.for_each(|part| self.push(part));
154    }
155}
156
157impl FromIterator<Symbol> for UrlPartsBuilder {
158    fn from_iter<T: IntoIterator<Item = Symbol>>(iter: T) -> Self {
159        // This code has to be duplicated from the `&str` impl because of
160        // `Symbol::as_str`'s lifetimes.
161        let iter = iter.into_iter();
162        let mut builder = Self::with_capacity_bytes(AVG_PART_LENGTH * iter.size_hint().0);
163        iter.for_each(|part| builder.push(part.as_str()));
164        builder
165    }
166}
167
168impl Extend<Symbol> for UrlPartsBuilder {
169    fn extend<T: IntoIterator<Item = Symbol>>(&mut self, iter: T) {
170        // This code has to be duplicated from the `&str` impl because of
171        // `Symbol::as_str`'s lifetimes.
172        let iter = iter.into_iter();
173        self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0);
174        iter.for_each(|part| self.push(part.as_str()));
175    }
176}
177
178#[cfg(test)]
179mod tests;