use crate::{
graph::{
feature::{CrossLink, FeatureGraph, FeatureId, FeatureMetadata, FeatureSet},
query_core::QueryParams,
DependencyDirection, FeatureGraphSpec, FeatureIx, PackageIx, PackageMetadata,
},
sorted_set::SortedSet,
Error, PackageId,
};
use itertools::Itertools;
use petgraph::graph::NodeIndex;
use std::collections::HashSet;
pub trait FeatureFilter<'g> {
fn accept(&mut self, graph: &FeatureGraph<'g>, feature_id: FeatureId<'g>) -> bool;
}
impl<'g, 'a, T> FeatureFilter<'g> for &'a mut T
where
T: FeatureFilter<'g>,
{
fn accept(&mut self, graph: &FeatureGraph<'g>, feature_id: FeatureId<'g>) -> bool {
(**self).accept(graph, feature_id)
}
}
impl<'g, 'a> FeatureFilter<'g> for Box<dyn FeatureFilter<'g> + 'a> {
fn accept(&mut self, graph: &FeatureGraph<'g>, feature_id: FeatureId<'g>) -> bool {
(**self).accept(graph, feature_id)
}
}
impl<'g, 'a> FeatureFilter<'g> for &'a mut dyn FeatureFilter<'g> {
fn accept(&mut self, graph: &FeatureGraph<'g>, feature_id: FeatureId<'g>) -> bool {
(**self).accept(graph, feature_id)
}
}
#[derive(Clone, Debug)]
pub struct FeatureFilterFn<F>(F);
impl<'g, F> FeatureFilterFn<F>
where
F: FnMut(&FeatureGraph<'g>, FeatureId<'g>) -> bool,
{
pub fn new(f: F) -> Self {
FeatureFilterFn(f)
}
}
impl<'g, F> FeatureFilter<'g> for FeatureFilterFn<F>
where
F: FnMut(&FeatureGraph<'g>, FeatureId<'g>) -> bool,
{
fn accept(&mut self, graph: &FeatureGraph<'g>, feature_id: FeatureId<'g>) -> bool {
(self.0)(graph, feature_id)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum StandardFeatures {
None,
Default,
All,
}
impl StandardFeatures {
pub const VALUES: &'static [Self; 3] = &[
StandardFeatures::None,
StandardFeatures::Default,
StandardFeatures::All,
];
}
impl<'g> FeatureFilter<'g> for StandardFeatures {
fn accept(&mut self, graph: &FeatureGraph<'g>, feature_id: FeatureId<'g>) -> bool {
match self {
StandardFeatures::None => {
feature_id.is_base()
}
StandardFeatures::Default => {
graph
.is_default_feature(feature_id)
.expect("feature IDs should be valid")
}
StandardFeatures::All => true,
}
}
}
pub fn feature_filter<'g: 'a, 'a>(
base: impl FeatureFilter<'g> + 'a,
features: impl IntoIterator<Item = &'a str>,
) -> impl FeatureFilter<'g> + 'a {
let mut base = base;
let features: HashSet<_> = features.into_iter().collect();
FeatureFilterFn::new(move |feature_graph, feature_id| {
if base.accept(feature_graph, feature_id) {
return true;
}
match feature_id.feature() {
Some(feature) => features.contains(feature),
None => {
false
}
}
})
}
pub fn feature_id_filter<'g: 'a, 'a>(
base: impl FeatureFilter<'g> + 'a,
feature_ids: impl IntoIterator<Item = impl Into<FeatureId<'a>>>,
) -> impl FeatureFilter<'g> + 'a {
let mut base = base;
let feature_ids: HashSet<_> = feature_ids
.into_iter()
.map(|feature_id| feature_id.into())
.collect();
FeatureFilterFn::new(move |feature_graph, feature_id| {
base.accept(feature_graph, feature_id) || feature_ids.contains(&feature_id)
})
}
#[derive(Clone, Debug)]
pub struct FeatureQuery<'g> {
pub(super) graph: FeatureGraph<'g>,
pub(in crate::graph) params: QueryParams<FeatureGraphSpec>,
}
assert_covariant!(FeatureQuery);
impl<'g> FeatureGraph<'g> {
pub fn query_workspace(&self, filter: impl FeatureFilter<'g>) -> FeatureQuery<'g> {
self.package_graph
.query_workspace()
.to_feature_query(filter)
}
pub fn query_directed<'a>(
&self,
feature_ids: impl IntoIterator<Item = impl Into<FeatureId<'a>>>,
dep_direction: DependencyDirection,
) -> Result<FeatureQuery<'g>, Error> {
match dep_direction {
DependencyDirection::Forward => self.query_forward(feature_ids),
DependencyDirection::Reverse => self.query_reverse(feature_ids),
}
}
pub fn query_forward<'a>(
&self,
feature_ids: impl IntoIterator<Item = impl Into<FeatureId<'a>>>,
) -> Result<FeatureQuery<'g>, Error> {
let feature_ids = feature_ids.into_iter().map(|feature_id| feature_id.into());
Ok(FeatureQuery {
graph: *self,
params: QueryParams::Forward(self.feature_ixs(feature_ids)?),
})
}
pub fn query_reverse<'a>(
&self,
feature_ids: impl IntoIterator<Item = impl Into<FeatureId<'a>>>,
) -> Result<FeatureQuery<'g>, Error> {
let feature_ids = feature_ids.into_iter().map(|feature_id| feature_id.into());
Ok(FeatureQuery {
graph: *self,
params: QueryParams::Reverse(self.feature_ixs(feature_ids)?),
})
}
pub(in crate::graph) fn query_from_parts(
&self,
feature_ixs: SortedSet<NodeIndex<FeatureIx>>,
direction: DependencyDirection,
) -> FeatureQuery<'g> {
let params = match direction {
DependencyDirection::Forward => QueryParams::Forward(feature_ixs),
DependencyDirection::Reverse => QueryParams::Reverse(feature_ixs),
};
FeatureQuery {
graph: *self,
params,
}
}
}
impl<'g> FeatureQuery<'g> {
pub fn graph(&self) -> &FeatureGraph<'g> {
&self.graph
}
pub fn direction(&self) -> DependencyDirection {
self.params.direction()
}
pub fn initials<'a>(
&'a self,
) -> impl Iterator<Item = FeatureMetadata<'g>> + ExactSizeIterator + 'a {
let graph = self.graph;
self.params
.initials()
.iter()
.map(move |feature_ix| graph.metadata_for_ix(*feature_ix))
}
pub fn initial_packages<'a>(&'a self) -> impl Iterator<Item = PackageMetadata<'g>> + 'a {
self.initials().map(|feature| feature.package()).dedup()
}
pub fn starts_from_package(&self, package_id: &PackageId) -> Result<bool, Error> {
let package_ix = self.graph.package_graph.package_ix(package_id)?;
Ok(self.starts_from_package_ix(package_ix))
}
pub fn starts_from<'a>(&self, feature_id: impl Into<FeatureId<'a>>) -> Result<bool, Error> {
Ok(self
.params
.has_initial(self.graph.feature_ix(feature_id.into())?))
}
pub fn resolve(self) -> FeatureSet<'g> {
FeatureSet::new(self)
}
pub fn resolve_with(self, resolver: impl FeatureResolver<'g>) -> FeatureSet<'g> {
FeatureSet::with_resolver(self, resolver)
}
pub fn resolve_with_fn(
self,
resolver_fn: impl FnMut(&FeatureQuery<'g>, CrossLink<'g>) -> bool,
) -> FeatureSet<'g> {
self.resolve_with(ResolverFn(resolver_fn))
}
pub(in crate::graph) fn starts_from_package_ix(
&self,
package_ix: NodeIndex<PackageIx>,
) -> bool {
self.graph
.feature_ixs_for_package_ix(package_ix)
.any(|feature_ix| self.params.has_initial(feature_ix))
}
}
pub trait FeatureResolver<'g> {
fn accept(&mut self, query: &FeatureQuery<'g>, link: CrossLink<'g>) -> bool;
}
impl<'g, 'a, T> FeatureResolver<'g> for &'a mut T
where
T: FeatureResolver<'g>,
{
fn accept(&mut self, query: &FeatureQuery<'g>, link: CrossLink<'g>) -> bool {
(**self).accept(query, link)
}
}
impl<'g, 'a> FeatureResolver<'g> for Box<dyn FeatureResolver<'g> + 'a> {
fn accept(&mut self, query: &FeatureQuery<'g>, link: CrossLink<'g>) -> bool {
(**self).accept(query, link)
}
}
impl<'g, 'a> FeatureResolver<'g> for &'a mut dyn FeatureResolver<'g> {
fn accept(&mut self, query: &FeatureQuery<'g>, link: CrossLink<'g>) -> bool {
(**self).accept(query, link)
}
}
#[derive(Clone, Debug)]
struct ResolverFn<F>(pub F);
impl<'g, F> FeatureResolver<'g> for ResolverFn<F>
where
F: FnMut(&FeatureQuery<'g>, CrossLink<'g>) -> bool,
{
fn accept(&mut self, query: &FeatureQuery<'g>, link: CrossLink<'g>) -> bool {
(self.0)(query, link)
}
}