| 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. |
| 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. |
| /// |
| /// 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. |
| const AVG_PART_LENGTH: usize = 8; |
| |
| 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; |