| use bstr::{BString, ByteSlice}; |
| use gix_glob::Pattern; |
| use kstring::{KString, KStringRef}; |
| |
| use crate::{ |
| search::{ |
| refmap::RefMapKey, Assignments, AttributeId, Attributes, MatchKind, Metadata, MetadataCollection, Outcome, |
| TrackedAssignment, Value, |
| }, |
| AssignmentRef, NameRef, StateRef, |
| }; |
| |
| /// Initialization |
| impl Outcome { |
| /// Initialize this instance to collect outcomes for all names in `collection`, which represents all possible attributes |
| /// or macros we may visit, and [`reset`][Self::reset()] it unconditionally. |
| /// |
| /// This must be called after each time `collection` changes. |
| pub fn initialize(&mut self, collection: &MetadataCollection) { |
| if self.matches_by_id.len() != collection.name_to_meta.len() { |
| let global_num_attrs = collection.name_to_meta.len(); |
| |
| self.matches_by_id.resize(global_num_attrs, Default::default()); |
| |
| // NOTE: This works only under the assumption that macros remain defined. |
| for (order, macro_attributes) in collection.iter().filter_map(|(_, meta)| { |
| (!meta.macro_attributes.is_empty()).then_some((meta.id.0, &meta.macro_attributes)) |
| }) { |
| self.matches_by_id[order].macro_attributes = macro_attributes.clone() |
| } |
| |
| for (name, id) in self.selected.iter_mut().filter(|(_, id)| id.is_none()) { |
| *id = collection.name_to_meta.get(name.as_str()).map(|meta| meta.id) |
| } |
| } |
| self.reset(); |
| } |
| |
| /// Like [`initialize()`][Self::initialize()], but limits the set of attributes to look for and fill in |
| /// to `attribute_names`. |
| /// Users of this instance should prefer to limit their search as this would allow it to finish earlier. |
| /// |
| /// Note that `attribute_names` aren't validated to be valid names here, as invalid names definitely will always be unspecified. |
| pub fn initialize_with_selection<'a>( |
| &mut self, |
| collection: &MetadataCollection, |
| attribute_names: impl IntoIterator<Item = impl Into<KStringRef<'a>>>, |
| ) { |
| self.initialize_with_selection_inner(collection, &mut attribute_names.into_iter().map(Into::into)) |
| } |
| |
| fn initialize_with_selection_inner( |
| &mut self, |
| collection: &MetadataCollection, |
| attribute_names: &mut dyn Iterator<Item = KStringRef<'_>>, |
| ) { |
| self.initialize(collection); |
| |
| self.selected.clear(); |
| self.selected.extend(attribute_names.map(|name| { |
| ( |
| name.to_owned(), |
| collection.name_to_meta.get(name.as_str()).map(|meta| meta.id), |
| ) |
| })); |
| self.reset_remaining(); |
| } |
| |
| /// Prepare for a new search over the known set of attributes by resetting our state. |
| pub fn reset(&mut self) { |
| self.matches_by_id.iter_mut().for_each(|item| item.r#match = None); |
| self.attrs_stack.clear(); |
| self.reset_remaining(); |
| } |
| |
| fn reset_remaining(&mut self) { |
| self.remaining = Some(if self.selected.is_empty() { |
| self.matches_by_id.len() |
| } else { |
| self.selected.iter().filter(|(_name, id)| id.is_some()).count() |
| }); |
| } |
| |
| /// A performance optimization which allows results from this instance to be efficiently copied over to `dest`. |
| /// For this to work, `collection` must be the one used to initialize our state, and `dest` should not have been initialized |
| /// with any meaningful collection initially, i.e. be empty the first time this method is called. |
| /// |
| /// Note that it's safe to call it multiple times, so that it can be called after this instance was used to store a search result. |
| pub fn copy_into(&self, collection: &MetadataCollection, dest: &mut Self) { |
| dest.initialize(collection); |
| dest.matches_by_id = self.matches_by_id.clone(); |
| if dest.patterns.len() != self.patterns.len() { |
| dest.patterns = self.patterns.clone(); |
| } |
| if dest.assignments.len() != self.assignments.len() { |
| dest.assignments = self.assignments.clone(); |
| } |
| if dest.source_paths.len() != self.source_paths.len() { |
| dest.source_paths = self.source_paths.clone(); |
| } |
| dest.remaining = self.remaining; |
| } |
| } |
| |
| /// Access |
| impl Outcome { |
| /// Return an iterator over all filled attributes we were initialized with. |
| /// |
| /// ### Note |
| /// |
| /// If [`initialize_with_selection`][Self::initialize_with_selection()] was used, |
| /// use [`iter_selected()`][Self::iter_selected()] instead. |
| /// |
| /// ### Deviation |
| /// |
| /// It's possible that the order in which the attribute are returned (if not limited to a set of attributes) isn't exactly |
| /// the same as what `git` provides. |
| /// Ours is in order of declaration, whereas `git` seems to list macros first somehow. Since the values are the same, this |
| /// shouldn't be an issue. |
| pub fn iter(&self) -> impl Iterator<Item = crate::search::Match<'_>> { |
| self.matches_by_id |
| .iter() |
| .filter_map(|item| item.r#match.as_ref().map(|m| m.to_outer(self))) |
| } |
| |
| /// Iterate over all matches of the attribute selection in their original order. |
| /// |
| /// This only yields values if this instance was initialized with [`Outcome::initialize_with_selection()`]. |
| pub fn iter_selected(&self) -> impl Iterator<Item = crate::search::Match<'_>> { |
| static DUMMY: Pattern = Pattern { |
| text: BString::new(Vec::new()), |
| mode: gix_glob::pattern::Mode::empty(), |
| first_wildcard_pos: None, |
| }; |
| self.selected.iter().map(|(name, id)| { |
| id.and_then(|id| self.matches_by_id[id.0].r#match.as_ref().map(|m| m.to_outer(self))) |
| .unwrap_or_else(|| crate::search::Match { |
| pattern: &DUMMY, |
| assignment: AssignmentRef { |
| name: NameRef::try_from(name.as_bytes().as_bstr()) |
| .unwrap_or_else(|_| NameRef("invalid".into())), |
| state: StateRef::Unspecified, |
| }, |
| kind: MatchKind::Attribute { macro_id: None }, |
| location: crate::search::MatchLocation { |
| source: None, |
| sequence_number: 0, |
| }, |
| }) |
| }) |
| } |
| |
| /// Obtain a match by the order of its attribute, if the order exists in our initialized attribute list and there was a match. |
| pub fn match_by_id(&self, id: AttributeId) -> Option<crate::search::Match<'_>> { |
| self.matches_by_id |
| .get(id.0) |
| .and_then(|m| m.r#match.as_ref().map(|m| m.to_outer(self))) |
| } |
| |
| /// Return `true` if there is nothing more to be done as all attributes were filled. |
| pub fn is_done(&self) -> bool { |
| self.remaining() == 0 |
| } |
| } |
| |
| /// Mutation |
| impl Outcome { |
| /// Fill all `attrs` and resolve them recursively if they are macros. Return `true` if there is no attribute left to be resolved and |
| /// we are totally done. |
| /// `pattern` is what matched a patch and is passed for contextual information, |
| /// providing `sequence_number` and `source` as well. |
| pub(crate) fn fill_attributes<'a>( |
| &mut self, |
| attrs: impl Iterator<Item = &'a TrackedAssignment>, |
| pattern: &gix_glob::Pattern, |
| source: Option<&std::path::PathBuf>, |
| sequence_number: usize, |
| ) -> bool { |
| self.attrs_stack.extend( |
| attrs |
| .filter(|attr| self.matches_by_id[attr.id.0].r#match.is_none()) |
| .map(|attr| (attr.id, attr.inner.clone(), None)), |
| ); |
| while let Some((id, assignment, parent_order)) = self.attrs_stack.pop() { |
| let slot = &mut self.matches_by_id[id.0]; |
| if slot.r#match.is_some() { |
| continue; |
| } |
| // Let's be explicit - this is only non-empty for macros. |
| let is_macro = !slot.macro_attributes.is_empty(); |
| |
| slot.r#match = Some(Match { |
| pattern: self.patterns.insert(pattern), |
| assignment: self.assignments.insert_owned(assignment), |
| kind: if is_macro { |
| MatchKind::Macro { |
| parent_macro_id: parent_order, |
| } |
| } else { |
| MatchKind::Attribute { macro_id: parent_order } |
| }, |
| location: MatchLocation { |
| source: source.map(|path| self.source_paths.insert(path)), |
| sequence_number, |
| }, |
| }); |
| if self.reduce_and_check_if_done(id) { |
| return true; |
| } |
| |
| if is_macro { |
| // TODO(borrowchk): one fine day we should be able to re-borrow `slot` without having to redo the array access. |
| let slot = &self.matches_by_id[id.0]; |
| self.attrs_stack.extend( |
| slot.macro_attributes |
| .iter() |
| .filter(|attr| self.matches_by_id[attr.id.0].r#match.is_none()) |
| .map(|attr| (attr.id, attr.inner.clone(), Some(id))), |
| ); |
| } |
| } |
| false |
| } |
| } |
| |
| impl Outcome { |
| /// Given a list of `attrs` by order, return true if at least one of them is not set |
| pub(crate) fn has_unspecified_attributes(&self, mut attrs: impl Iterator<Item = AttributeId>) -> bool { |
| attrs.any(|order| self.matches_by_id[order.0].r#match.is_none()) |
| } |
| /// Return the amount of attributes haven't yet been found. |
| /// |
| /// If this number reaches 0, then the search can be stopped as there is nothing more to fill in. |
| pub(crate) fn remaining(&self) -> usize { |
| self.remaining |
| .expect("BUG: instance must be initialized for each search set") |
| } |
| |
| fn reduce_and_check_if_done(&mut self, attr: AttributeId) -> bool { |
| if self.selected.is_empty() |
| || self |
| .selected |
| .iter() |
| .any(|(_name, id)| id.map_or(false, |id| id == attr)) |
| { |
| *self.remaining.as_mut().expect("initialized") -= 1; |
| } |
| self.is_done() |
| } |
| } |
| |
| impl std::fmt::Debug for Outcome { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| struct AsDisplay<'a>(&'a dyn std::fmt::Display); |
| impl std::fmt::Debug for AsDisplay<'_> { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| self.0.fmt(f) |
| } |
| } |
| |
| let mut dbg = f.debug_tuple("Outcome"); |
| if self.selected.is_empty() { |
| for match_ in self.iter() { |
| dbg.field(&AsDisplay(&match_.assignment)); |
| } |
| } else { |
| for match_ in self.iter_selected() { |
| dbg.field(&AsDisplay(&match_.assignment)); |
| } |
| } |
| dbg.finish() |
| } |
| } |
| |
| /// Mutation |
| impl MetadataCollection { |
| /// Assign order ids to each attribute either in macros (along with macros themselves) or attributes of patterns, and store |
| /// them in this collection. |
| /// |
| /// Must be called before querying matches. |
| pub fn update_from_list(&mut self, list: &mut gix_glob::search::pattern::List<Attributes>) { |
| for pattern in &mut list.patterns { |
| match &mut pattern.value { |
| Value::MacroAssignments { id: order, assignments } => { |
| *order = self.id_for_macro( |
| pattern |
| .pattern |
| .text |
| .to_str() |
| .expect("valid macro names are always UTF8 and this was verified"), |
| assignments, |
| ); |
| } |
| Value::Assignments(assignments) => { |
| self.assign_order_to_attributes(assignments); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Access |
| impl MetadataCollection { |
| /// Return an iterator over the contents of the map in an easy-to-consume form. |
| pub fn iter(&self) -> impl Iterator<Item = (&str, &Metadata)> { |
| self.name_to_meta.iter().map(|(k, v)| (k.as_str(), v)) |
| } |
| } |
| |
| impl MetadataCollection { |
| pub(crate) fn id_for_macro(&mut self, name: &str, attrs: &mut Assignments) -> AttributeId { |
| let order = match self.name_to_meta.get_mut(name) { |
| Some(meta) => meta.id, |
| None => { |
| let order = AttributeId(self.name_to_meta.len()); |
| self.name_to_meta.insert( |
| KString::from_ref(name), |
| Metadata { |
| id: order, |
| macro_attributes: Default::default(), |
| }, |
| ); |
| order |
| } |
| }; |
| |
| self.assign_order_to_attributes(attrs); |
| self.name_to_meta.get_mut(name).expect("just added").macro_attributes = attrs.clone(); |
| |
| order |
| } |
| pub(crate) fn id_for_attribute(&mut self, name: &str) -> AttributeId { |
| match self.name_to_meta.get(name) { |
| Some(meta) => meta.id, |
| None => { |
| let order = AttributeId(self.name_to_meta.len()); |
| self.name_to_meta.insert(KString::from_ref(name), order.into()); |
| order |
| } |
| } |
| } |
| pub(crate) fn assign_order_to_attributes(&mut self, attributes: &mut [TrackedAssignment]) { |
| for TrackedAssignment { |
| id: order, |
| inner: crate::Assignment { name, .. }, |
| } in attributes |
| { |
| *order = self.id_for_attribute(&name.0); |
| } |
| } |
| } |
| |
| impl From<AttributeId> for Metadata { |
| fn from(order: AttributeId) -> Self { |
| Metadata { |
| id: order, |
| macro_attributes: Default::default(), |
| } |
| } |
| } |
| |
| impl MatchKind { |
| /// return the id of the macro that resolved us, or `None` if that didn't happen. |
| pub fn source_id(&self) -> Option<AttributeId> { |
| match self { |
| MatchKind::Attribute { macro_id: id } | MatchKind::Macro { parent_macro_id: id } => *id, |
| } |
| } |
| } |
| |
| /// A version of `Match` without references. |
| #[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] |
| pub struct Match { |
| /// The glob pattern itself, like `/target/*`. |
| pub pattern: RefMapKey, |
| /// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern. |
| pub assignment: RefMapKey, |
| /// Additional information about the kind of match. |
| pub kind: MatchKind, |
| /// Information about the location of the match. |
| pub location: MatchLocation, |
| } |
| |
| impl Match { |
| fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::Match<'a> { |
| crate::search::Match { |
| pattern: out.patterns.resolve(self.pattern).expect("pattern still present"), |
| assignment: out |
| .assignments |
| .resolve(self.assignment) |
| .expect("assignment present") |
| .as_ref(), |
| kind: self.kind, |
| location: self.location.to_outer(out), |
| } |
| } |
| } |
| |
| /// A version of `MatchLocation` without references. |
| #[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] |
| pub struct MatchLocation { |
| /// The path to the source from which the pattern was loaded, or `None` if it was specified by other means. |
| pub source: Option<RefMapKey>, |
| /// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided. |
| pub sequence_number: usize, |
| } |
| |
| impl MatchLocation { |
| fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::MatchLocation<'a> { |
| crate::search::MatchLocation { |
| source: self |
| .source |
| .and_then(|source| out.source_paths.resolve(source).map(AsRef::as_ref)), |
| sequence_number: self.sequence_number, |
| } |
| } |
| } |