use super::Pointer;
#[cfg(feature = "ignore_case")]
use std::cmp::Ordering;
use std::{borrow::Cow, cmp::min, collections::VecDeque, num::NonZero};
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) enum InnerNode {
#[default]
Root,
Trie(String),
Name(String),
Index(u64),
}
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct Node {
pub(crate) child_index: Option<NonZero<u32>>,
pub(crate) num_trie_children: u32,
pub(crate) num_name_children: u32,
pub(crate) num_index_children: u32,
pub(crate) inner: InnerNode,
pub(crate) match_index: Option<usize>,
}
const _: [(); 64] = [(); std::mem::size_of::<Node>()];
impl Node {
fn new_name(name: impl Into<String>, match_index: Option<usize>) -> Self {
Self {
child_index: None,
num_trie_children: 0,
num_name_children: 0,
num_index_children: 0,
inner: InnerNode::Name(name.into()),
match_index,
}
}
fn new_trie(name: impl Into<String>, match_index: Option<usize>) -> Self {
Self {
child_index: None,
num_trie_children: 0,
num_name_children: 0,
num_index_children: 0,
inner: InnerNode::Trie(name.into()),
match_index,
}
}
fn new_index(index: u64, match_index: Option<usize>) -> Self {
Self {
child_index: None,
num_trie_children: 0,
num_name_children: 0,
num_index_children: 0,
inner: InnerNode::Index(index),
match_index,
}
}
fn with_match_index(mut self, match_index: usize) -> Self {
self.match_index = Some(match_index);
self
}
#[cfg(test)]
fn with_child_index(mut self, child_index: u32) -> Self {
self.child_index = Some(NonZero::new(child_index).unwrap());
self
}
#[cfg(test)]
fn with_trie_children(mut self, n: u32) -> Self {
self.num_trie_children = n;
self
}
#[cfg(test)]
fn with_name_children(mut self, n: u32) -> Self {
self.num_name_children = n;
self
}
#[cfg(test)]
fn with_index_children(mut self, n: u32) -> Self {
self.num_index_children = n;
self
}
pub(crate) fn name_part(&self) -> &str {
match self.inner {
InnerNode::Root => panic!("root node does not have a name part: {self:?}"),
InnerNode::Trie(ref s) | InnerNode::Name(ref s) => s,
InnerNode::Index(_) => panic!("index node does not have a name part: {self:?}"),
}
}
}
#[derive(Debug)]
struct WorkPiece {
node: Node,
start_index: usize,
level: usize,
pointer_index: usize,
prefix_len: usize,
}
impl WorkPiece {
fn new(
node: Node,
start_index: usize,
level: usize,
pointer_index: usize,
prefix_len: usize,
) -> Self {
Self {
node,
start_index,
level,
pointer_index,
prefix_len,
}
}
}
#[derive(Debug)]
struct ParsedPointer {
pointer: Pointer,
ref_tokens: Vec<String>,
}
impl ParsedPointer {
fn new(pointer: Pointer, #[cfg(feature = "ignore_case")] ignore_case: bool) -> Self {
let ref_tokens = pointer.ref_tokens();
#[cfg(not(feature = "ignore_case"))]
let ref_tokens = ref_tokens.map(Cow::into_owned).collect::<Vec<_>>();
#[cfg(feature = "ignore_case")]
let ref_tokens = if !ignore_case {
ref_tokens.map(Cow::into_owned).collect::<Vec<_>>()
} else {
ref_tokens.map(Self::case_fold).collect::<Vec<_>>()
};
Self {
pointer,
ref_tokens,
}
}
#[cfg(feature = "ignore_case")]
fn case_fold<'a>(ref_token: Cow<'a, str>) -> String {
debug_assert!(matches!(ref_token, Cow::Borrowed(_)));
if ref_token.is_ascii() {
ref_token.to_lowercase()
} else {
caseless::default_case_fold_str(ref_token.as_ref())
}
}
fn has_more_tokens(&self, level: usize) -> bool {
level < self.ref_tokens.len() - 1
}
fn has_ref_token(&self, level: usize) -> bool {
level < self.ref_tokens.len()
}
fn ref_token_of(&self, level: usize) -> &str {
&self.ref_tokens[level]
}
}
#[derive(Debug)]
struct Builder {
nodes: Vec<Node>,
parents: Vec<u32>,
#[cfg(feature = "ignore_case")]
ignore_case: bool,
parsed_pointers: Vec<ParsedPointer>,
queue: VecDeque<WorkPiece>,
node: Node,
start_index: usize,
level: usize,
pointer_index: Option<usize>,
prefix_len: usize,
}
macro_rules! enqueue_child {
($self:expr, $child:expr, $start_index:expr, $child_level:expr, $pointer_index:expr, $prefix_len:expr) => {{
let current_index = $self
.nodes
.len()
.try_into()
.expect("node count cannot exceed `u32::MAX`");
$self.parents.push(current_index);
match $child.inner {
InnerNode::Root => unreachable!("logic error: can't enqueue root node as a child"),
InnerNode::Trie(_) => $self.node.num_trie_children += 1,
InnerNode::Name(_) => $self.node.num_name_children += 1,
InnerNode::Index(_) => $self.node.num_index_children += 1,
}
if $self.node.child_index.is_none() {
$self.node.child_index = $self.child_node_index();
}
$self.queue.push_back(WorkPiece::new(
$child,
$start_index,
$child_level,
$pointer_index,
$prefix_len,
));
}};
}
macro_rules! enqueue_trie_children {
($self:expr, $lead_iter:expr, $prev_prefix_len:expr, $new_node:ident) => {{
let trail_iter = $lead_iter
.clone()
.into_iter()
.skip(1)
.map(Some)
.chain(std::iter::once(None));
let mut lead_iter = $lead_iter.into_iter().peekable();
if let Some((prev_index, prev)) = lead_iter.peek() {
let (mut prev_index, mut prev_common_len) = (
*prev_index,
prev.ref_token_of($self.level).len() - $prev_prefix_len,
);
for ((_, lead), trail) in lead_iter.zip(trail_iter) {
let lead_token: &str = &lead.ref_token_of($self.level)[$prev_prefix_len..];
let (trail_index, trail_token) = match trail {
Some((i, pp)) => (i, &pp.ref_token_of($self.level)[$prev_prefix_len..]),
None => (0, ""),
};
match Self::common_prefix_len(lead_token, trail_token) {
0 => {
let child_pointer_index = prev_index;
let part_len = prev_common_len;
let name =
$self.parsed_pointers[child_pointer_index].ref_token_of($self.level);
let name_part = &name[$prev_prefix_len..$prev_prefix_len + part_len];
let is_complete_token = $prev_prefix_len + part_len == name.len();
let has_more_tokens =
$self.parsed_pointers[child_pointer_index].has_more_tokens($self.level);
let child = Node::$new_node(
name_part,
$self.matched(is_complete_token, child_pointer_index),
);
let start_index = if is_complete_token && !has_more_tokens {
child_pointer_index + 1
} else {
child_pointer_index
};
enqueue_child!(
$self,
child,
start_index,
$self.level,
child_pointer_index,
$prev_prefix_len + part_len
);
prev_index = trail_index;
prev_common_len = trail_token.len();
}
n => prev_common_len = min(n, prev_common_len),
}
}
}
}};
}
impl Builder {
fn new(pointers: Vec<Pointer>, #[cfg(feature = "ignore_case")] ignore_case: bool) -> Self {
let mut parsed_pointers: Vec<ParsedPointer> = pointers
.into_iter()
.map(|p| {
ParsedPointer::new(
p,
#[cfg(feature = "ignore_case")]
ignore_case,
)
})
.collect();
#[cfg(not(feature = "ignore_case"))]
parsed_pointers.sort_unstable_by(|a, b| a.ref_tokens.cmp(&b.ref_tokens));
#[cfg(feature = "ignore_case")]
parsed_pointers.sort_unstable_by(|a, b| match a.ref_tokens.cmp(&b.ref_tokens) {
Ordering::Equal if ignore_case => a.pointer.cmp(&b.pointer),
o => o,
});
parsed_pointers.dedup_by(|a, b| a.ref_tokens == b.ref_tokens);
let nodes: Vec<Node> = Vec::new();
let parents: Vec<u32> = Vec::new();
let mut root = Node::default();
let mut first_child_index = 0;
if let Some(first) = parsed_pointers.first()
&& first.ref_tokens.is_empty()
{
root = root.with_match_index(0);
first_child_index = 1;
}
let queue = VecDeque::new();
Builder {
nodes,
parents,
#[cfg(feature = "ignore_case")]
ignore_case,
parsed_pointers,
queue,
node: root,
start_index: first_child_index,
level: 0,
pointer_index: None,
prefix_len: 0,
}
}
fn build(mut self) -> Group {
loop {
let mut is_incomplete = false;
if let InnerNode::Trie(_) | InnerNode::Name(_) = &self.node.inner {
is_incomplete = self.prefix_len < self.ref_token().len();
if self.prefix_len > 0 {
self.enqueue_trie_children();
}
if !is_incomplete {
self.level += 1; self.prefix_len = 0;
}
}
if !is_incomplete {
let end_index = self
.parsed_pointers
.iter()
.skip(self.start_index)
.position(|pp: &ParsedPointer| {
!pp.ref_tokens.starts_with(&self.ref_tokens()[..self.level])
})
.map(|i| self.start_index + i)
.unwrap_or(self.parsed_pointers.len());
if self.start_index < end_index {
self.enqueue_name_children(end_index);
self.enqueue_index_children(end_index);
}
}
self.nodes.push(self.node);
if let Some(next) = self.queue.pop_front() {
self.node = next.node;
self.start_index = next.start_index;
self.level = next.level;
self.pointer_index = Some(next.pointer_index);
self.prefix_len = next.prefix_len;
} else {
break Group {
nodes: self.nodes,
parents: self.parents,
pointers: self
.parsed_pointers
.into_iter()
.map(|pp| pp.pointer)
.collect(),
#[cfg(feature = "ignore_case")]
ignore_case: self.ignore_case,
};
}
}
}
fn enqueue_trie_children(&mut self) {
let ref_tokens = &self.parsed_pointers[self.pointer_index.unwrap()].ref_tokens;
let parent_tokens = &ref_tokens[..self.level];
let prefix = &ref_tokens[self.level][0..self.prefix_len];
let lead_iter = self
.parsed_pointers
.iter()
.enumerate()
.skip(self.start_index)
.take_while(|(_, pp)| {
pp.ref_tokens.starts_with(parent_tokens)
&& pp.has_ref_token(self.level)
&& pp.ref_token_of(self.level).starts_with(prefix)
})
.filter(|(_, pp)| pp.ref_token_of(self.level) != prefix);
enqueue_trie_children!(self, lead_iter, self.prefix_len, new_trie);
}
fn enqueue_name_children(&mut self, end_index: usize) {
let lead_iter = self
.parsed_pointers
.iter()
.enumerate()
.take(end_index)
.skip(self.start_index);
enqueue_trie_children!(self, lead_iter, 0, new_name);
}
fn enqueue_index_children(&mut self, end_index: usize) {
struct LocalChild {
index: u64,
start_index: usize,
pointer_index: usize,
}
let mut local_buf = Vec::new();
let mut prev = None;
for (pointer_index, parsed_pointer) in self
.parsed_pointers
.iter()
.enumerate()
.take(end_index)
.skip(self.start_index)
{
let ref_token = parsed_pointer.ref_token_of(self.level);
if let Ok(index) = ref_token.parse::<u64>()
&& (ref_token.len() == 1 || !ref_token.starts_with('0'))
&& !matches!(prev, Some(x) if x == index)
{
prev = Some(index);
let start_index = if parsed_pointer.has_more_tokens(self.level) {
pointer_index
} else {
pointer_index + 1
};
local_buf.push(LocalChild {
index,
start_index,
pointer_index,
});
}
}
local_buf.sort_unstable_by_key(|n| n.index);
for n in local_buf {
let child = Node::new_index(n.index, self.matched(true, n.pointer_index));
enqueue_child!(
self,
child,
n.start_index,
self.level + 1,
n.pointer_index,
0
);
}
}
fn matched(&self, is_complete_token: bool, pointer_index: usize) -> Option<usize> {
let parsed_pointer = &self.parsed_pointers[pointer_index];
if is_complete_token && !parsed_pointer.has_more_tokens(self.level) {
Some(pointer_index)
} else {
None
}
}
fn ref_token(&self) -> &str {
&self.ref_tokens()[self.level]
}
fn ref_tokens(&self) -> &[String] {
match self.pointer_index {
Some(i) => &self.parsed_pointers[i].ref_tokens,
None => &[],
}
}
fn child_node_index(&self) -> Option<NonZero<u32>> {
let child_index = self.nodes.len() + self.queue.len() + 1;
Some(
NonZero::new(
child_index
.try_into()
.expect("node count cannot exceed `u32::MAX`"),
)
.unwrap(),
)
}
#[inline]
fn common_prefix_len(a: &str, b: &str) -> usize {
a.bytes().zip(b.bytes()).take_while(|(a, b)| a == b).count()
}
}
#[cfg_attr(
feature = "ignore_case",
doc = "[`from_pointers_ignore_case`][method@Self::from_pointers_ignore_case]"
)]
#[cfg_attr(not(feature = "ignore_case"), doc = "`from_pointers_ignore_case`.")]
#[derive(Clone, Debug)]
pub struct Group {
#[allow(unused)]
pub(crate) nodes: Vec<Node>,
#[allow(unused)]
pub(crate) parents: Vec<u32>,
#[allow(unused)]
pub(crate) pointers: Vec<Pointer>,
#[cfg(feature = "ignore_case")]
#[allow(unused)]
pub(crate) ignore_case: bool,
}
impl Group {
pub fn from_pointer(pointer: Pointer) -> Self {
Self::from_pointers([pointer])
}
pub fn from_pointers<I: IntoIterator<Item = Pointer>>(pointers: I) -> Self {
let pointers = pointers.into_iter().collect();
Builder::new(
pointers,
#[cfg(feature = "ignore_case")]
false,
)
.build()
}
#[cfg(feature = "ignore_case")]
pub fn from_pointers_ignore_case<I: IntoIterator<Item = Pointer>>(pointers: I) -> Self {
let pointers = pointers.into_iter().collect();
Builder::new(pointers, true).build()
}
}
impl AsRef<Group> for Group {
fn as_ref(&self) -> &Self {
self
}
}
impl From<Pointer> for Group {
fn from(pointer: Pointer) -> Self {
Self::from_pointer(pointer)
}
}
impl FromIterator<Pointer> for Group {
fn from_iter<T: IntoIterator<Item = Pointer>>(iter: T) -> Self {
Self::from_pointers(iter)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use std::fmt;
#[rstest]
#[case::name_no_match(Node::new_name("foo", None), "foo")]
#[case::name_match(Node::new_name("bar", Some(0)), "bar")]
#[case::trie_no_match(Node::new_trie("baz", None), "baz")]
#[case::trie_no_match(Node::new_trie("qux", Some(0)), "qux")]
fn test_node_name_part_ok(#[case] node: Node, #[case] expect: &str) {
assert_eq!(expect, node.name_part());
}
#[test]
#[should_panic(expected = "root node does not have a name part")]
fn test_node_name_part_panic_root() {
let _ = Node::default().name_part();
}
#[test]
#[should_panic(expected = "index node does not have a name part")]
fn test_node_name_part_panic_index() {
let _ = Node::new_index(0, None).name_part();
}
#[rstest]
#[case("", "", 0)]
#[case("", "a", 0)]
#[case("a", "a", 1)]
#[case("a", "b", 0)]
#[case("a", "A", 0)]
#[case("foo", "fO", 1)]
#[case("foo", "foO", 2)]
#[case("foo", "foo", 3)]
#[case("foo", "fool", 3)]
#[case("foo", "foolish", 3)]
fn test_builder_common_prefix_len(#[case] a: &str, #[case] b: &str, #[case] expect: usize) {
let len_a_b = Builder::common_prefix_len(a, b);
let len_b_a = Builder::common_prefix_len(b, a);
assert_eq!(expect, len_a_b);
assert_eq!(expect, len_b_a);
}
#[rstest]
#[case::root(Pointer::default(), [Node::default().with_match_index(0)], [])]
#[case::single_empty("/", [Node::default().with_child_index(1).with_name_children(1), Node::new_name("", Some(0))], [0])]
#[case::single_a("/a", [Node::default().with_child_index(1).with_name_children(1), Node::new_name("a", Some(0))], [0])]
#[case::single_0("/0", [
Node::default().with_child_index(1).with_name_children(1).with_index_children(1),
Node::new_name("0", Some(0)),
Node::new_index(0, Some(0)),
], [0, 0])]
#[case::single_00("/00", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("00", Some(0)),
], [0])]
#[case::single_00("/01", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("01", Some(0)),
], [0])]
#[case::single_minus_1("/-1", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("-1", Some(0)),
], [0])]
#[case::single_u64_max(format!("/{}", u64::MAX), [
Node::default().with_child_index(1).with_name_children(1).with_index_children(1),
Node::new_name(format!("{}", usize::MAX), Some(0)),
Node::new_index(u64::MAX, Some(0)),
], [0, 0])]
#[case::single_u64_max_plus_1(format!("/{}", u64::MAX as u128 + 1), [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name(format!("{}", u64::MAX as u128 + 1), Some(0)),
], [0])]
#[case::single_escape_tilde("/~0", [Node::default().with_child_index(1).with_name_children(1), Node::new_name("~", Some(0))], [0])]
#[case::single_escape_slash("/~1", [Node::default().with_child_index(1).with_name_children(1), Node::new_name("/", Some(0))], [0])]
#[case::single_no_case_fold("/Straße", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("Straße", Some(0)),
], [0])]
#[case::double_empty("//", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("", None).with_child_index(2).with_name_children(1),
Node::new_name("", Some(0))
], [0, 1])]
#[case::slash_a_slash_empty("/a/", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", None).with_child_index(2).with_name_children(1),
Node::new_name("", Some(0))
], [0, 1])]
#[case::slash_0_slash_empty("/0/", [
Node::default().with_child_index(1).with_name_children(1).with_index_children(1),
Node::new_name("0", None).with_child_index(3).with_name_children(1),
Node::new_index(0, None).with_child_index(4).with_name_children(1),
Node::new_name("", Some(0)),
Node::new_name("", Some(0)),
], [0, 0, 1, 2])]
#[case::slash_empty_slash_a("//a", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("", None).with_child_index(2).with_name_children(1),
Node::new_name("a", Some(0)),
], [0, 1])]
#[case::slash_0_slash_empty("//0", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("", None).with_child_index(2).with_name_children(1).with_index_children(1),
Node::new_name("0", Some(0)),
Node::new_index(0, Some(0)),
], [0, 1, 1])]
#[case::slash_a_slash_b("/a/b", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", None).with_child_index(2).with_name_children(1),
Node::new_name("b", Some(0)),
], [0, 1])]
#[case::slash_0_slash_1("/0/1", [
Node::default().with_child_index(1).with_name_children(1).with_index_children(1),
Node::new_name("0", None).with_child_index(3).with_name_children(1).with_index_children(1),
Node::new_index(0, None).with_child_index(5).with_name_children(1).with_index_children(1),
Node::new_name("1", Some(0)),
Node::new_index(1, Some(0)),
Node::new_name("1", Some(0)),
Node::new_index(1, Some(0)),
], [0, 0, 1, 1, 2, 2])]
#[case::triple_empty("///", [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("", None).with_child_index(2).with_name_children(1),
Node::new_name("", None).with_child_index(3).with_name_children(1),
Node::new_name("", Some(0)),
], [0, 1, 2])]
fn test_group_from_pointer<P, I, J>(
#[case] pointer: P,
#[case] expect_nodes: I,
#[case] expect_parents: J,
) where
P: TryInto<Pointer>,
<P as TryInto<Pointer>>::Error: fmt::Debug,
I: IntoIterator<Item = Node>,
J: IntoIterator<Item = u32>,
{
let pointer = pointer.try_into().unwrap();
let expect_nodes = expect_nodes.into_iter().collect::<Vec<_>>();
let expect_parents = expect_parents.into_iter().collect::<Vec<_>>();
let g = Group::from_pointer(pointer);
assert_eq!(
expect_nodes,
g.nodes,
"node array mismatch: {} expected nodes (left) do not match {} actual nodes (right)",
expect_nodes.len(),
g.nodes.len(),
);
assert_eq!(expect_parents, g.parents);
}
#[rstest]
#[case::empty([], [Node::default()], [])]
#[case::two_duplicate_roots(["", ""], [Node::default().with_match_index(0)], [])]
#[case::two_duplicate_slash_empty(["/", "/"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("", Some(0)),
], [0])]
#[case::two_duplicate_slash_a(["/a", "/a"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)),
], [0])]
#[case::two_root_and_slash_empty(["", "/"], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("", Some(1)),
], [0])]
#[case::two_slash_empty_and_root(["/", ""], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("", Some(1)),
], [0])]
#[case::two_root_and_slash_a(["", "/a"], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("a", Some(1)),
], [0])]
#[case::two_root_and_slash_a_slash_b(["", "/a/b"], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("a", None).with_child_index(2).with_name_children(1),
Node::new_name("b", Some(1)),
], [0, 1])]
#[case::two_slash_a_and_root(["/a", ""], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("a", Some(1)),
], [0])]
#[case::two_slash_empty_and_tokens_foo_bar_baz_13_tilde_slash(["/", "/foo/bar/baz/13/~0~1"], [
Node::default().with_child_index(1).with_name_children(2),
Node::new_name("", Some(0)),
Node::new_name("foo", None).with_child_index(3).with_name_children(1),
Node::new_name("bar", None).with_child_index(4).with_name_children(1),
Node::new_name("baz", None).with_child_index(5).with_name_children(1).with_index_children(1),
Node::new_name("13", None).with_child_index(7).with_name_children(1),
Node::new_index(13, None).with_child_index(8).with_name_children(1),
Node::new_name("~/", Some(1)),
Node::new_name("~/", Some(1)),
], [0, 0, 2, 3, 4, 4, 5, 6])]
#[case::two_slash_a_and_slash_b(["/a", "/b"], [
Node::default().with_child_index(1).with_name_children(2),
Node::new_name("a", Some(0)),
Node::new_name("b", Some(1)),
], [0, 0])]
#[case::two_slash_a_and_slash_b(["/b", "/a"], [
Node::default().with_child_index(1).with_name_children(2),
Node::new_name("a", Some(0)),
Node::new_name("b", Some(1)),
], [0, 0])]
#[case::two_slash_a_and_slash_aa(["/a", "/aa"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_trie_children(1),
Node::new_trie("a", Some(1)),
], [0, 1])]
#[case::two_slash_a_and_slash_ab(["/a", "/ab"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_trie_children(1),
Node::new_trie("b", Some(1)),
], [0, 1])]
#[case::two_slash_0_and_slash_09(["/0", "/09"], [
Node::default().with_child_index(1).with_name_children(1).with_index_children(1),
Node::new_name("0", Some(0)).with_child_index(3).with_trie_children(1),
Node::new_index(0, Some(0)),
Node::new_trie("9", Some(1)),
], [0, 0, 1])]
#[case::two_slash_0_and_slash_0_slash_1(["/0", "/0/1"], [
Node::default().with_child_index(1).with_name_children(1).with_index_children(1),
Node::new_name("0", Some(0)).with_child_index(3).with_name_children(1).with_index_children(1),
Node::new_index(0, Some(0)).with_child_index(5).with_name_children(1).with_index_children(1),
Node::new_name("1", Some(1)),
Node::new_index(1, Some(1)),
Node::new_name("1", Some(1)),
Node::new_index(1, Some(1)),
], [0, 0, 1, 1, 2, 2])]
#[case::two_slash_ab_and_slash_ac(["/ab", "/ac"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", None).with_child_index(2).with_trie_children(2),
Node::new_trie("b", Some(0)),
Node::new_trie("c", Some(1)),
], [0, 1, 1])]
#[case::two_slash_f_slash_oo_and_slash_f_slash_ob(["/f/oo", "/f/ob"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("f", None).with_child_index(2).with_name_children(1),
Node::new_name("o", None).with_child_index(3).with_trie_children(2),
Node::new_trie("b", Some(0)),
Node::new_trie("o", Some(1)),
], [0, 1, 2, 2])]
#[case::two_index_node_sort(["/10", "/2"], [
Node::default().with_child_index(1).with_name_children(2).with_index_children(2),
Node::new_name("10", Some(0)),
Node::new_name("2", Some(1)),
Node::new_index(2, Some(1)),
Node::new_index(10, Some(0)),
], [0, 0, 0, 0])]
#[case::three_triplicate_empty(["", "", ""], [Node::default().with_match_index(0)], [])]
#[case::three_duplicate_slash_empty(["", "/", "/"], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("", Some(1)),
], [0])]
#[case::three_root_and_slash_empty_and_slash_a(["", "/", "/a"], [
Node::default().with_child_index(1).with_name_children(2).with_match_index(0),
Node::new_name("", Some(1)),
Node::new_name("a", Some(2)),
], [0, 0])]
#[case::three_slash_aa_slash_a_root(["/aa", "/a", ""], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("a", Some(1)).with_child_index(2).with_trie_children(1),
Node::new_trie("a", Some(2)),
], [0, 1])]
#[case::three_slash_bb_slash_b_slash_a(["/bb", "/ba", "/a"], [
Node::default().with_child_index(1).with_name_children(2),
Node::new_name("a", Some(0)),
Node::new_name("b", None).with_child_index(3).with_trie_children(2),
Node::new_trie("a", Some(1)),
Node::new_trie("b", Some(2)),
], [0, 0, 2, 2])]
#[case::three_slash_aa_slash_a_slash_ab(["/aa", "/a", "/ab"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_trie_children(2),
Node::new_trie("a", Some(1)),
Node::new_trie("b", Some(2)),
], [0, 1, 1])]
#[case::three_slash_a_slash_ab_slash_abc(["/a", "/ab", "/abc"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_trie_children(1),
Node::new_trie("b", Some(1)).with_child_index(3).with_trie_children(1),
Node::new_trie("c", Some(2))
], [0, 1, 2])]
#[case::three_slash_a_slash_ab_path_ab_c(["/a", "/ab", "/ab/c"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_trie_children(1),
Node::new_trie("b", Some(1)).with_child_index(3).with_name_children(1),
Node::new_name("c", Some(2))
], [0, 1, 2])]
#[case::three_slash_a_path_a_b_path_a_bc(["/a", "/a/b", "/a/bc"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_name_children(1),
Node::new_name("b", Some(1)).with_child_index(3).with_trie_children(1),
Node::new_trie("c", Some(2)),
], [0, 1, 2])]
#[case::three_slash_a_path_a_b_path_a_b_c(["/a", "/a/b", "/a/b/c"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_name_children(1),
Node::new_name("b", Some(1)).with_child_index(3).with_name_children(1),
Node::new_name("c", Some(2)),
], [0, 1, 2])]
#[case::three_path_a_b_path_a_c_slash_ab(["/a/b", "/a/c", "/ab"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", None).with_child_index(2).with_trie_children(1).with_name_children(2),
Node::new_trie("b", Some(2)),
Node::new_name("b", Some(0)),
Node::new_name("c", Some(1)),
], [0, 1, 1, 1])]
#[case::three_path_a_b_c_path_a_b_d_path_a_e(["/a/b/c", "/a/b/d", "/a/e"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", None).with_child_index(2).with_name_children(2),
Node::new_name("b", None).with_child_index(4).with_name_children(2),
Node::new_name("e", Some(2)),
Node::new_name("c", Some(0)),
Node::new_name("d", Some(1)),
], [0, 1, 1, 2, 2])]
#[case::three_slash_a_slash_0_1_2(["/a/0", "/a/1", "/a/2"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", None).with_child_index(2).with_name_children(3).with_index_children(3),
Node::new_name("0", Some(0)),
Node::new_name("1", Some(1)),
Node::new_name("2", Some(2)),
Node::new_index(0, Some(0)),
Node::new_index(1, Some(1)),
Node::new_index(2, Some(2)),
], [0, 1, 1, 1, 1, 1, 1])]
#[case::three_slash_foo_slash_fob_slash_fox(["/foo", "/fob", "/fox"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("fo", None).with_child_index(2).with_trie_children(3),
Node::new_trie("b", Some(0)),
Node::new_trie("o", Some(1)),
Node::new_trie("x", Some(2)),
], [0, 1, 1, 1])]
#[case::three_slash_abc_slash_abd_slash_acd(["/abc", "/abd", "/acd"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", None).with_child_index(2).with_trie_children(2),
Node::new_trie("b", None).with_child_index(4).with_trie_children(2),
Node::new_trie("cd", Some(2)),
Node::new_trie("c", Some(0)),
Node::new_trie("d", Some(1)),
], [0, 1, 1, 2, 2])]
#[case::three_path_foo_bar_path_foo_baz_path_fool_bar(["/foo/bar", "/foo/baz", "/fool/bar"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("foo", None).with_child_index(2).with_trie_children(1).with_name_children(1),
Node::new_trie("l", None).with_child_index(4).with_name_children(1),
Node::new_name("ba", None).with_child_index(5).with_trie_children(2),
Node::new_name("bar", Some(2)),
Node::new_trie("r", Some(0)),
Node::new_trie("z", Some(1)),
], [0, 1, 1, 2, 3, 3])]
#[case::three_slash_foo_slash_foobar_slash_foobaz(["/foo", "/foobar", "/foobaz"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("foo", Some(0)).with_child_index(2).with_trie_children(1),
Node::new_trie("ba", None).with_child_index(3).with_trie_children(2),
Node::new_trie("r", Some(1)),
Node::new_trie("z", Some(2)),
], [0, 1, 2, 2])]
#[case::four_with_root(["", "/a/b", "/a/b/c/21de", "/a/b/c/21"], [
Node::default().with_child_index(1).with_name_children(1).with_match_index(0),
Node::new_name("a", None).with_child_index(2).with_name_children(1),
Node::new_name("b", Some(1)).with_child_index(3).with_name_children(1),
Node::new_name("c", None).with_child_index(3).with_child_index(4).with_name_children(1).with_index_children(1),
Node::new_name("21", Some(2)).with_child_index(6).with_trie_children(1),
Node::new_index(21, Some(2)),
Node::new_trie("de", Some(3)),
], [0, 1, 2, 3, 3, 4])]
#[case::four_with_empty(["/", "/a/b", "/c/d", "/c/d~1"], [
Node::default().with_child_index(1).with_name_children(3),
Node::new_name("", Some(0)),
Node::new_name("a", None).with_child_index(4).with_name_children(1),
Node::new_name("c", None).with_child_index(5).with_name_children(1),
Node::new_name("b", Some(1)),
Node::new_name("d", Some(2)).with_child_index(6).with_trie_children(1),
Node::new_trie("/", Some(3)),
], [0, 0, 0, 2, 3, 5])]
#[case::four_slash_a_slash_ab_slash_abc_slash_ac(["/a", "/ab", "/abc", "/ac"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("a", Some(0)).with_child_index(2).with_trie_children(2),
Node::new_trie("b", Some(1)).with_child_index(4).with_trie_children(1),
Node::new_trie("c", Some(3)),
Node::new_trie("c", Some(2)),
], [0, 1, 1, 2])]
#[case::big1(["", "/0", "/1", "/1/1", "/1/3", "/10", "/3", "/3/0"], [
// Root.
/* 0 */ Node::default().with_match_index(0).with_child_index(1).with_name_children(3).with_index_children(4),
// Level 1.
/* 1 */ Node::new_name("0", Some(1)),
/* 2 */ Node::new_name("1", Some(2)).with_child_index(8).with_trie_children(1).with_name_children(2).with_index_children(2),
/* 3 */ Node::new_name("3", Some(6)).with_child_index(13).with_name_children(1).with_index_children(1),
/* 4 */ Node::new_index(0, Some(1)),
/* 5 */ Node::new_index(1, Some(2)).with_child_index(15).with_name_children(2).with_index_children(2),
/* 6 */ Node::new_index(3, Some(6)).with_child_index(19).with_name_children(1).with_index_children(1),
/* 7 */ Node::new_index(10, Some(5)),
// Level 2.
/* 8 */ Node::new_trie("0", Some(5)),
/* 9 */ Node::new_name("1", Some(3)),
/* 10 */ Node::new_name("3", Some(4)),
/* 11 */ Node::new_index(1, Some(3)),
/* 12 */ Node::new_index(3, Some(4)),
/* 13 */ Node::new_name("0", Some(7)),
/* 14 */ Node::new_index(0, Some(7)),
/* 15 */ Node::new_name("1", Some(3)),
/* 16 */ Node::new_name("3", Some(4)),
/* 17 */ Node::new_index(1, Some(3)),
/* 18 */ Node::new_index(3, Some(4)),
/* 19 */ Node::new_name("0", Some(7)),
/* 20 */ Node::new_index(0, Some(7)),
], [0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 3, 3, 5, 5, 5, 5, 6, 6])]
#[case::big2(["", "/0", "/bar", "/foo", "/foo", "/foo/0", "/foo/1/ish", "/foo/baz", "/fool", "/fool/ish", "/fool/ish", "/foolish", "/foolish/ness", "/foolishness/~0", "/foot", "/qux/corge"], [
// Root.
/* 0 */ Node::default().with_child_index(1).with_name_children(4).with_index_children(1).with_match_index(0),
// Level 1.
/* 1 */ Node::new_name("0", Some(1)),
/* 2 */ Node::new_name("bar", Some(2)),
/* 3 */ Node::new_name("foo", Some(3)).with_child_index(6).with_trie_children(2).with_name_children(3).with_index_children(2),
/* 4 */ Node::new_name("qux", None).with_child_index(13).with_name_children(1),
/* 5 */ Node::new_index(0, Some(1)),
// Level 2.
/* 6 */ Node::new_trie("l", Some(7)).with_child_index(14).with_trie_children(1).with_name_children(1),
/* 7 */ Node::new_trie("t", Some(12)),
/* 8 */ Node::new_name("0", Some(4)),
/* 9 */ Node::new_name("1", None).with_child_index(16).with_name_children(1),
/* 10 */ Node::new_name("baz", Some(6)),
/* 11 */ Node::new_index(0, Some(4)),
/* 12 */ Node::new_index(1, None).with_child_index(17).with_name_children(1),
/* 13 */ Node::new_name("corge", Some(13)),
// Level 3.
/* 14 */ Node::new_trie("ish", Some(9)).with_child_index(18).with_trie_children(1).with_name_children(1),
/* 15 */ Node::new_name("ish", Some(8)),
/* 16 */ Node::new_name("ish", Some(5)),
/* 17 */ Node::new_name("ish", Some(5)),
// Level 4.
/* 18 */ Node::new_trie("ness", None).with_child_index(20).with_name_children(1),
/* 19 */ Node::new_name("ness", Some(10)),
// Level 5.
/* 20 */ Node::new_name("~", Some(11)),
], [0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 6, 6, 9, 12, 14, 14, 18])]
fn test_group_from_pointers<I, J, K>(
#[case] pointers: I,
#[case] expect_nodes: J,
#[case] expect_parents: K,
) where
I: IntoIterator<Item = &'static str>,
J: IntoIterator<Item = Node>,
K: IntoIterator<Item = u32>,
{
let pointers = pointers
.into_iter()
.enumerate()
.map(|(i, p)| {
p.try_into()
.unwrap_or_else(|err| panic!("invalid pointer {p:?} at index {i}: {err}"))
})
.collect::<Vec<_>>();
let expect_nodes = expect_nodes.into_iter().collect::<Vec<_>>();
let expect_parents = expect_parents.into_iter().collect::<Vec<_>>();
let g = Group::from_pointers(pointers);
assert_eq!(
expect_nodes,
g.nodes,
"node array mismatch: {} expected nodes (left) do not match {} actual nodes (right)",
expect_nodes.len(),
g.nodes.len(),
);
assert_eq!(expect_parents, g.parents);
}
#[rstest]
#[cfg(feature = "ignore_case")]
#[case(["/strasse"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("strasse", Some(0)),
], [0])]
#[case(["/STRASSE"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("strasse", Some(0)),
], [0])]
#[case(["/Straße"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("strasse", Some(0)),
], [0])]
#[case(["/Straße", "/strasse"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("strasse", Some(0)),
], [0])]
#[case(["/straße", "/Strasbourg", "/strauss"], [
Node::default().with_child_index(1).with_name_children(1),
Node::new_name("stra", None).with_child_index(2).with_trie_children(2),
Node::new_trie("s", None).with_child_index(4).with_trie_children(2),
Node::new_trie("uss", Some(2)),
Node::new_trie("bourg", Some(0)),
Node::new_trie("se", Some(1)),
], [0, 1, 1, 2, 2])]
fn test_group_from_pointers_ignore_case<I, J, K>(
#[case] pointers: I,
#[case] expect_nodes: J,
#[case] expect_parents: K,
) where
I: IntoIterator<Item = &'static str>,
J: IntoIterator<Item = Node>,
K: IntoIterator<Item = u32>,
{
let pointers = pointers
.into_iter()
.enumerate()
.map(|(i, p)| {
p.try_into()
.unwrap_or_else(|err| panic!("invalid pointer {p:?} at index {i}: {err}"))
})
.collect::<Vec<_>>();
let expect_nodes = expect_nodes.into_iter().collect::<Vec<_>>();
let expect_parents = expect_parents.into_iter().collect::<Vec<_>>();
let g = Group::from_pointers_ignore_case(pointers);
assert_eq!(
expect_nodes,
g.nodes,
"node array mismatch: {} expected nodes (left) do not match {} actual nodes (right)",
expect_nodes.len(),
g.nodes.len(),
);
assert_eq!(expect_parents, g.parents);
}
#[test]
fn test_group_from_trait_from_pointer() {
let g: Group = Pointer::default().into();
assert_eq!(vec![Node::default().with_match_index(0)], g.nodes);
assert_eq!(0, g.parents.len());
}
#[rstest]
#[case([])]
#[case([Pointer::default()])]
#[case([Pointer::from_static("/")])]
#[case([Pointer::from_static("/a")])]
#[case([Pointer::from_static("/a")])]
#[case([Pointer::default(), Pointer::from_static("/"), Pointer::from_static("/a")])]
fn test_group_from_iterator_trait<I>(#[case] pointers: I)
where
I: IntoIterator<Item = Pointer> + Clone,
{
let expect: Vec<Pointer> = pointers.clone().into_iter().collect();
let group: Group = pointers.into_iter().collect();
assert_eq!(expect, group.pointers);
}
}