-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is a type for efficiently and easily constructing the part of a URL after the domain: `nightly/core/str/struct.Bytes.html`. It allows simplifying some code and avoiding some allocations in the `href_*` functions. It will also allow making `Cache.paths` et al. use `Symbol` without having to allocate `String`s in the `href_*` functions. `String`s would be necessary otherwise because `Symbol::as_str()` returns `SymbolStr`, whose `Deref<Target = str>` impl requires the `str` to not outlive it. This is the primary motivation for the addition of `UrlPartsBuilder`.
- Loading branch information
Showing
5 changed files
with
192 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,44 @@ | ||
use crate::html::format::href_relative_parts; | ||
|
||
fn assert_relative_path(expected: &[&str], relative_to_fqp: &[&str], fqp: &[&str]) { | ||
fn assert_relative_path(expected: &str, relative_to_fqp: &[&str], fqp: &[&str]) { | ||
let relative_to_fqp: Vec<String> = relative_to_fqp.iter().copied().map(String::from).collect(); | ||
let fqp: Vec<String> = fqp.iter().copied().map(String::from).collect(); | ||
assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp)); | ||
assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp).finish()); | ||
} | ||
|
||
#[test] | ||
fn href_relative_parts_basic() { | ||
let relative_to_fqp = &["std", "vec"]; | ||
let fqp = &["std", "iter"]; | ||
assert_relative_path(&["..", "iter"], relative_to_fqp, fqp); | ||
assert_relative_path("../iter", relative_to_fqp, fqp); | ||
} | ||
#[test] | ||
fn href_relative_parts_parent_module() { | ||
let relative_to_fqp = &["std", "vec"]; | ||
let fqp = &["std"]; | ||
assert_relative_path(&[".."], relative_to_fqp, fqp); | ||
assert_relative_path("..", relative_to_fqp, fqp); | ||
} | ||
#[test] | ||
fn href_relative_parts_different_crate() { | ||
let relative_to_fqp = &["std", "vec"]; | ||
let fqp = &["core", "iter"]; | ||
assert_relative_path(&["..", "..", "core", "iter"], relative_to_fqp, fqp); | ||
assert_relative_path("../../core/iter", relative_to_fqp, fqp); | ||
} | ||
#[test] | ||
fn href_relative_parts_same_module() { | ||
let relative_to_fqp = &["std", "vec"]; | ||
let fqp = &["std", "vec"]; | ||
assert_relative_path(&[], relative_to_fqp, fqp); | ||
assert_relative_path("", relative_to_fqp, fqp); | ||
} | ||
#[test] | ||
fn href_relative_parts_child_module() { | ||
let relative_to_fqp = &["std"]; | ||
let fqp = &["std", "vec"]; | ||
assert_relative_path(&["vec"], relative_to_fqp, fqp); | ||
assert_relative_path("vec", relative_to_fqp, fqp); | ||
} | ||
#[test] | ||
fn href_relative_parts_root() { | ||
let relative_to_fqp = &[]; | ||
let fqp = &["std"]; | ||
assert_relative_path(&["std"], relative_to_fqp, fqp); | ||
assert_relative_path("std", relative_to_fqp, fqp); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/// 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)] | ||
crate struct UrlPartsBuilder { | ||
buf: String, | ||
} | ||
|
||
impl UrlPartsBuilder { | ||
/// Create an empty buffer. | ||
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"); | ||
/// ``` | ||
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"); | ||
/// ``` | ||
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 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"); | ||
/// ``` | ||
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. | ||
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. | ||
/// | ||
/// This is intentionally on the lower end to avoid overallocating. | ||
const AVG_PART_LENGTH: usize = 5; | ||
|
||
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)); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
use super::*; | ||
|
||
fn t(builder: UrlPartsBuilder, expect: &str) { | ||
assert_eq!(builder.finish(), expect); | ||
} | ||
|
||
#[test] | ||
fn empty() { | ||
t(UrlPartsBuilder::new(), ""); | ||
} | ||
|
||
#[test] | ||
fn singleton() { | ||
t(UrlPartsBuilder::singleton("index.html"), "index.html"); | ||
} | ||
|
||
#[test] | ||
fn push_several() { | ||
let mut builder = UrlPartsBuilder::new(); | ||
builder.push("core"); | ||
builder.push("str"); | ||
builder.push("struct.Bytes.html"); | ||
t(builder, "core/str/struct.Bytes.html"); | ||
} | ||
|
||
#[test] | ||
fn push_front_empty() { | ||
let mut builder = UrlPartsBuilder::new(); | ||
builder.push_front("page.html"); | ||
t(builder, "page.html"); | ||
} | ||
|
||
#[test] | ||
fn push_front_non_empty() { | ||
let mut builder = UrlPartsBuilder::new(); | ||
builder.push("core"); | ||
builder.push("str"); | ||
builder.push("struct.Bytes.html"); | ||
builder.push_front("nightly"); | ||
t(builder, "nightly/core/str/struct.Bytes.html"); | ||
} | ||
|
||
#[test] | ||
fn collect() { | ||
t(["core", "str"].into_iter().collect(), "core/str"); | ||
t(["core", "str", "struct.Bytes.html"].into_iter().collect(), "core/str/struct.Bytes.html"); | ||
} | ||
|
||
#[test] | ||
fn extend() { | ||
let mut builder = UrlPartsBuilder::singleton("core"); | ||
builder.extend(["str", "struct.Bytes.html"]); | ||
t(builder, "core/str/struct.Bytes.html"); | ||
} |