blob: 7cea3bd1970a896f9b984167aeafe1dc58bc25f3 [file] [log] [blame]
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,
}
}
}