use crate::filter::By;
use crate::{AccessKitNode, NodeT};
use std::collections::BTreeSet;
use std::iter::FusedIterator;
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn query_all<'tree, Node: NodeT<'tree> + 'tree>(
node: Node,
by: By<'tree>,
) -> impl DoubleEndedIterator<Item = Node> + FusedIterator<Item = Node> + 'tree {
let should_filter_labels = by.should_filter_labels();
let results = node
.children_maybe_recursive(by.recursive)
.filter(move |node| by.matches(&node.accesskit_node()));
let nodes = results.collect::<Vec<_>>();
let labels: BTreeSet<_> = if should_filter_labels {
nodes
.iter()
.flat_map(|node| node.accesskit_node().labelled_by())
.map(|node| node.id())
.collect()
} else {
BTreeSet::new()
};
nodes.into_iter().filter(move |node| {
if should_filter_labels {
!labels.contains(&node.accesskit_node().id())
} else {
true
}
})
}
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn get_all<'tree, Node: NodeT<'tree> + 'tree>(
node: Node,
by: By<'tree>,
) -> impl DoubleEndedIterator<Item = Node> + FusedIterator<Item = Node> + 'tree {
let debug_query = by.debug_clone_without_predicate();
let mut iter = query_all(node.clone(), by).peekable();
assert!(
iter.peek().is_some(),
"No nodes found matching the query:\n{debug_query:#?}\n\nOn node:\n{node:#?}"
);
iter
}
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn query<'tree, Node: NodeT<'tree> + 'tree>(node: Node, by: By<'tree>) -> Option<Node> {
let debug_query = by.debug_clone_without_predicate();
let mut iter = query_all(node, by);
let result = iter.next();
if let Some(second) = iter.next() {
let first = result?;
panic!(
"Found two or more nodes matching the query: \n{debug_query:#?}\n\nFirst node:\n{first:#?}\n\nSecond node: {second:#?}\
\n\nIf you were expecting multiple nodes, use query_all instead of query."
);
}
result
}
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn get<'tree, Node: NodeT<'tree> + 'tree>(node: Node, by: By<'tree>) -> Node {
let debug_query = by.debug_clone_without_predicate();
let option = query(node.clone(), by);
if let Some(node) = option {
node
} else {
panic!("No nodes found matching the query:\n{debug_query:#?}\n\nOn node:\n{node:#?}");
}
}
macro_rules! impl_helper {
(
$match_doc:literal,
$query_all_label:ident,
$get_all_label:ident,
$query_label:ident,
$get_label:ident,
$node:ident,
($($args:ident: $arg_ty:ty),*),
$by_expr:expr,
$(#[$extra_doc:meta])*
) => {
#[doc = $match_doc]
$(#[$extra_doc])*
#[track_caller]
fn $query_all_label(
&'node self, $($args: $arg_ty),*
) -> impl DoubleEndedIterator<Item = $node> + FusedIterator<Item = $node> + 'tree {
query_all(self.queryable_node(), $by_expr)
}
#[doc = $match_doc]
$(#[$extra_doc])*
#[track_caller]
fn $get_all_label(
&'node self, $($args: $arg_ty),*
) -> impl DoubleEndedIterator<Item = $node> + FusedIterator<Item = $node> + 'tree {
get_all(self.queryable_node(), $by_expr)
}
#[doc = $match_doc]
$(#[$extra_doc])*
#[track_caller]
fn $query_label(&'node self, $($args: $arg_ty),*) -> Option<$node> {
query(self.queryable_node(), $by_expr)
}
#[doc = $match_doc]
$(#[$extra_doc])*
#[track_caller]
fn $get_label(&'node self, $($args: $arg_ty),*) -> $node {
get(self.queryable_node(), $by_expr)
}
};
}
pub trait Queryable<'tree, 'node, Node: NodeT<'tree> + 'tree> {
fn queryable_node(&'node self) -> Node;
impl_helper!(
"the node matches the given [`By`] filter.",
query_all,
get_all,
query,
get,
Node,
(by: By<'tree>),
by,
);
impl_helper!(
"the node label exactly matches given label.",
query_all_by_label,
get_all_by_label,
query_by_label,
get_by_label,
Node,
(label: &'tree str),
By::new().label(label),
#[doc = ""]
#[doc = "If a node is labelled by another node, the label node will not be included in the results."]
);
impl_helper!(
"the node label contains the given substring.",
query_all_by_label_contains,
get_all_by_label_contains,
query_by_label_contains,
get_by_label_contains,
Node,
(label: &'tree str),
By::new().label_contains(label),
#[doc = ""]
#[doc = "If a node is labelled by another node, the label node will not be included in the results."]
);
impl_helper!(
"the node role and label exactly match the given role and label.",
query_all_by_role_and_label,
get_all_by_role_and_label,
query_by_role_and_label,
get_by_role_and_label,
Node,
(role: accesskit::Role, label: &'tree str),
By::new().role(role).label(label),
#[doc = ""]
#[doc = "If a node is labelled by another node, the label node will not be included in the results."]
);
impl_helper!(
"the node role matches the given role.",
query_all_by_role,
get_all_by_role,
query_by_role,
get_by_role,
Node,
(role: accesskit::Role),
By::new().role(role),
);
impl_helper!(
"the node value exactly matches the given value.",
query_all_by_value,
get_all_by_value,
query_by_value,
get_by_value,
Node,
(value: &'tree str),
By::new().value(value),
);
impl_helper!(
"the node matches the given predicate.",
query_all_by,
get_all_by,
query_by,
get_by,
Node,
(f: impl Fn(&AccessKitNode<'_>) -> bool + 'tree),
By::new().predicate(f),
);
}