use crate::{
Error, PackageId,
graph::{
DependencyDirection, FeatureIx, PackageMetadata,
feature::{ConditionalLink, FeatureGraph, FeatureId, FeatureLabel, FeatureSet},
},
};
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, T> FeatureFilter<'g> for &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> FeatureFilter<'g> for Box<dyn FeatureFilter<'g> + '_> {
fn accept(&mut self, graph: &FeatureGraph<'g>, feature_id: FeatureId<'g>) -> bool {
(**self).accept(graph, feature_id)
}
}
impl<'g> FeatureFilter<'g> for &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 named_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.label() {
FeatureLabel::Named(feature) => features.contains(feature),
_ => {
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) initials: FeatureSet<'g>,
pub(super) direction: DependencyDirection,
}
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());
let feature_ixs: Vec<_> = self.feature_ixs(feature_ids)?;
Ok(self.query_from_parts(feature_ixs, DependencyDirection::Forward))
}
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());
let feature_ixs: Vec<_> = self.feature_ixs(feature_ids)?;
Ok(self.query_from_parts(feature_ixs, DependencyDirection::Reverse))
}
pub(in crate::graph) fn query_from_parts(
&self,
feature_ixs: impl IntoIterator<Item = NodeIndex<FeatureIx>>,
direction: DependencyDirection,
) -> FeatureQuery<'g> {
FeatureQuery {
initials: FeatureSet::from_ixs(*self, feature_ixs),
direction,
}
}
}
impl<'g> FeatureQuery<'g> {
pub fn graph(&self) -> &FeatureGraph<'g> {
self.initials.graph()
}
pub fn direction(&self) -> DependencyDirection {
self.direction
}
pub fn initials(&self) -> &FeatureSet<'g> {
&self.initials
}
pub fn initial_packages<'a>(&'a self) -> impl Iterator<Item = PackageMetadata<'g>> + 'a {
let graph = *self.graph();
self.initials
.sorted_ixs()
.map(move |feature_ix| graph.metadata_for_ix(feature_ix).package())
.dedup()
}
pub fn starts_from_package(&self, package_id: &PackageId) -> Result<bool, Error> {
self.initials.contains_package(package_id)
}
pub fn resolve(self) -> FeatureSet<'g> {
FeatureSet::new(self)
}
pub fn resolve_with(self, visitor: impl FeatureLinkVisitor<'g>) -> FeatureSet<'g> {
FeatureSet::with_link_visitor(self, visitor)
}
pub fn resolve_with_fn(
self,
visitor_fn: impl FnMut(&FeatureLinkContext<'g>, ConditionalLink<'g>) -> bool,
) -> FeatureSet<'g> {
self.resolve_with(LinkVisitorFn(visitor_fn))
}
}
#[derive(Clone, Debug)]
pub struct FeatureLinkContext<'g> {
query: FeatureQuery<'g>,
}
impl<'g> FeatureLinkContext<'g> {
pub(super) fn new(query: FeatureQuery<'g>) -> Self {
Self { query }
}
pub fn query(&self) -> &FeatureQuery<'g> {
&self.query
}
pub fn direction(&self) -> DependencyDirection {
self.query.direction()
}
pub fn starts_from_initial(&self, link: &ConditionalLink<'g>) -> bool {
let feature_ix = match self.direction() {
DependencyDirection::Forward => link.from().feature_ix(),
DependencyDirection::Reverse => link.to().feature_ix(),
};
self.query.initials.contains_ix(feature_ix)
}
}
pub trait FeatureLinkVisitor<'g> {
fn visit_link(&mut self, cx: &FeatureLinkContext<'g>, link: ConditionalLink<'g>) -> bool;
}
impl<'g, T> FeatureLinkVisitor<'g> for &mut T
where
T: FeatureLinkVisitor<'g>,
{
fn visit_link(&mut self, cx: &FeatureLinkContext<'g>, link: ConditionalLink<'g>) -> bool {
(**self).visit_link(cx, link)
}
}
impl<'g> FeatureLinkVisitor<'g> for Box<dyn FeatureLinkVisitor<'g> + '_> {
fn visit_link(&mut self, cx: &FeatureLinkContext<'g>, link: ConditionalLink<'g>) -> bool {
(**self).visit_link(cx, link)
}
}
impl<'g> FeatureLinkVisitor<'g> for &mut dyn FeatureLinkVisitor<'g> {
fn visit_link(&mut self, cx: &FeatureLinkContext<'g>, link: ConditionalLink<'g>) -> bool {
(**self).visit_link(cx, link)
}
}
#[derive(Clone, Debug)]
struct LinkVisitorFn<F>(pub F);
impl<'g, F> FeatureLinkVisitor<'g> for LinkVisitorFn<F>
where
F: FnMut(&FeatureLinkContext<'g>, ConditionalLink<'g>) -> bool,
{
fn visit_link(&mut self, cx: &FeatureLinkContext<'g>, link: ConditionalLink<'g>) -> bool {
(self.0)(cx, link)
}
}