summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html/url_parts_builder.rs
blob: 1e6af6af63cc469154b6a6c825b152eec594cf49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use std::fmt::{self, Write};

use rustc_span::Symbol;

/// 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)]
pub(crate) struct UrlPartsBuilder {
    buf: String,
}

impl UrlPartsBuilder {
    /// Create an empty buffer.
    #[allow(dead_code)]
    pub(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");
    /// ```
    pub(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");
    /// ```
    pub(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 buffer, using [`format!`]'s formatting syntax.
    ///
    /// # Examples
    ///
    /// Basic usage (equivalent to the example for [`UrlPartsBuilder::push`]):
    ///
    /// ```ignore (private-type)
    /// let mut builder = UrlPartsBuilder::new();
    /// builder.push("core");
    /// builder.push("str");
    /// builder.push_fmt(format_args!("{}.{}.html", "struct", "Bytes"));
    /// assert_eq!(builder.finish(), "core/str/struct.Bytes.html");
    /// ```
    pub(crate) fn push_fmt(&mut self, args: fmt::Arguments<'_>) {
        if !self.buf.is_empty() {
            self.buf.push('/');
        }
        self.buf.write_fmt(args).unwrap()
    }

    /// 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");
    /// ```
    pub(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.
    pub(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, and for [estimating item path lengths].
///
/// The value `8` was chosen for two main reasons:
///
/// * It seems like a good guess for the average part length.
/// * jemalloc's size classes are all multiples of eight,
///   which means that the amount of memory it allocates will often match
///   the amount requested, avoiding wasted bytes.
///
/// [estimating item path lengths]: estimate_item_path_byte_length
const AVG_PART_LENGTH: usize = 8;

/// Estimate the number of bytes in an item's path, based on how many segments it has.
///
/// **Note:** This is only to be used with, e.g., [`String::with_capacity()`];
/// the return value is just a rough estimate.
pub(crate) const fn estimate_item_path_byte_length(segment_count: usize) -> usize {
    AVG_PART_LENGTH * segment_count
}

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));
    }
}

impl FromIterator<Symbol> for UrlPartsBuilder {
    fn from_iter<T: IntoIterator<Item = Symbol>>(iter: T) -> Self {
        // This code has to be duplicated from the `&str` impl because of
        // `Symbol::as_str`'s lifetimes.
        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.as_str()));
        builder
    }
}

impl Extend<Symbol> for UrlPartsBuilder {
    fn extend<T: IntoIterator<Item = Symbol>>(&mut self, iter: T) {
        // This code has to be duplicated from the `&str` impl because of
        // `Symbol::as_str`'s lifetimes.
        let iter = iter.into_iter();
        self.buf.reserve(AVG_PART_LENGTH * iter.size_hint().0);
        iter.for_each(|part| self.push(part.as_str()));
    }
}

#[cfg(test)]
mod tests;