gix-attributes-0.23.1/.cargo_vcs_info.json0000644000000001540000000000100140720ustar { "git": { "sha1": "8ce49129a75e21346ceedf7d5f87fa3a34b024e1" }, "path_in_vcs": "gix-attributes" }gix-attributes-0.23.1/Cargo.toml0000644000000075060000000000100121000ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "gix-attributes" version = "0.23.1" authors = ["Sebastian Thiel "] build = false include = [ "src/**/*", "LICENSE-*", ] autobins = false autoexamples = false autotests = false autobenches = false description = "A crate of the gitoxide project dealing .gitattributes files" readme = false license = "MIT OR Apache-2.0" repository = "https://github.com/GitoxideLabs/gitoxide" [package.metadata.docs.rs] all-features = true features = ["document-features"] [lib] name = "gix_attributes" path = "src/lib.rs" doctest = false [dependencies.bstr] version = "1.3.0" features = [ "std", "unicode", ] default-features = false [dependencies.document-features] version = "0.2.1" optional = true [dependencies.gix-glob] version = "^0.17.0" [dependencies.gix-path] version = "^0.10.13" [dependencies.gix-quote] version = "^0.4.14" [dependencies.gix-trace] version = "^0.1.11" [dependencies.kstring] version = "2.0.0" [dependencies.serde] version = "1.0.114" features = ["derive"] optional = true default-features = false [dependencies.smallvec] version = "1.10.0" [dependencies.thiserror] version = "2.0.0" [dependencies.unicode-bom] version = "2.0.3" [dev-dependencies] [features] serde = [ "dep:serde", "bstr/serde", "gix-glob/serde", "kstring/serde", ] [lints.clippy] bool_to_int_with_if = "allow" borrow_as_ptr = "allow" cast_lossless = "allow" cast_possible_truncation = "allow" cast_possible_wrap = "allow" cast_precision_loss = "allow" cast_sign_loss = "allow" checked_conversions = "allow" copy_iterator = "allow" default_trait_access = "allow" doc_markdown = "allow" empty_docs = "allow" enum_glob_use = "allow" explicit_deref_methods = "allow" explicit_into_iter_loop = "allow" explicit_iter_loop = "allow" filter_map_next = "allow" fn_params_excessive_bools = "allow" from_iter_instead_of_collect = "allow" if_not_else = "allow" ignored_unit_patterns = "allow" implicit_clone = "allow" inconsistent_struct_constructor = "allow" inefficient_to_string = "allow" inline_always = "allow" items_after_statements = "allow" iter_not_returning_iterator = "allow" iter_without_into_iter = "allow" manual_assert = "allow" manual_is_variant_and = "allow" manual_let_else = "allow" manual_string_new = "allow" many_single_char_names = "allow" match_bool = "allow" match_same_arms = "allow" match_wild_err_arm = "allow" match_wildcard_for_single_variants = "allow" missing_errors_doc = "allow" missing_panics_doc = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" mut_mut = "allow" naive_bytecount = "allow" needless_for_each = "allow" needless_pass_by_value = "allow" needless_raw_string_hashes = "allow" no_effect_underscore_binding = "allow" option_option = "allow" range_plus_one = "allow" redundant_else = "allow" return_self_not_must_use = "allow" should_panic_without_expect = "allow" similar_names = "allow" single_match_else = "allow" stable_sort_primitive = "allow" struct_excessive_bools = "allow" struct_field_names = "allow" too_long_first_doc_paragraph = "allow" too_many_lines = "allow" transmute_ptr_to_ptr = "allow" trivially_copy_pass_by_ref = "allow" unnecessary_join = "allow" unnecessary_wraps = "allow" unreadable_literal = "allow" unused_self = "allow" used_underscore_binding = "allow" wildcard_imports = "allow" [lints.clippy.pedantic] level = "warn" priority = -1 [lints.rust] gix-attributes-0.23.1/Cargo.toml.orig000064400000000000000000000024471046102023000155600ustar 00000000000000lints.workspace = true [package] name = "gix-attributes" version = "0.23.1" repository = "https://github.com/GitoxideLabs/gitoxide" license = "MIT OR Apache-2.0" description = "A crate of the gitoxide project dealing .gitattributes files" authors = ["Sebastian Thiel "] edition = "2021" include = ["src/**/*", "LICENSE-*"] rust-version = "1.65" [lib] doctest = false [features] ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde = ["dep:serde", "bstr/serde", "gix-glob/serde", "kstring/serde"] [dependencies] gix-path = { version = "^0.10.13", path = "../gix-path" } gix-quote = { version = "^0.4.14", path = "../gix-quote" } gix-glob = { version = "^0.17.0", path = "../gix-glob" } gix-trace = { version = "^0.1.11", path = "../gix-trace" } bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"] } smallvec = "1.10.0" kstring = "2.0.0" unicode-bom = { version = "2.0.3" } thiserror = "2.0.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } document-features = { version = "0.2.1", optional = true } [dev-dependencies] gix-testtools = { path = "../tests/tools" } gix-fs = { path = "../gix-fs" } [package.metadata.docs.rs] all-features = true features = ["document-features"] gix-attributes-0.23.1/LICENSE-APACHE000064400000000000000000000247461046102023000146230ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gix-attributes-0.23.1/LICENSE-MIT000064400000000000000000000017771046102023000143320ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gix-attributes-0.23.1/src/assignment.rs000064400000000000000000000027311046102023000161720ustar 00000000000000use std::fmt::Write; use bstr::ByteSlice; use crate::{Assignment, AssignmentRef, NameRef, StateRef}; impl<'a> AssignmentRef<'a> { pub(crate) fn new(name: NameRef<'a>, state: StateRef<'a>) -> AssignmentRef<'a> { AssignmentRef { name, state } } /// Turn this reference into its owned counterpart. pub fn to_owned(self) -> Assignment { self.into() } } impl<'a> From> for Assignment { fn from(a: AssignmentRef<'a>) -> Self { Assignment { name: a.name.to_owned(), state: a.state.to_owned(), } } } impl<'a> Assignment { /// Provide a ref type to this owned instance. pub fn as_ref(&'a self) -> AssignmentRef<'a> { AssignmentRef::new(self.name.as_ref(), self.state.as_ref()) } } impl std::fmt::Display for AssignmentRef<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.state { StateRef::Set => f.write_str(self.name.as_str()), StateRef::Unset => { f.write_char('-')?; f.write_str(self.name.as_str()) } StateRef::Value(v) => { f.write_str(self.name.as_str())?; f.write_char('=')?; f.write_str(v.as_bstr().to_str_lossy().as_ref()) } StateRef::Unspecified => { f.write_char('!')?; f.write_str(self.name.as_str()) } } } } gix-attributes-0.23.1/src/lib.rs000064400000000000000000000111031046102023000145610ustar 00000000000000//! Parse `.gitattribute` files and provide utilities to match against them. //! //! ## Feature Flags #![cfg_attr( all(doc, feature = "document-features"), doc = ::document_features::document_features!() )] #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] #![deny(missing_docs, rust_2018_idioms)] #![forbid(unsafe_code)] pub use gix_glob as glob; use kstring::{KString, KStringRef}; mod assignment; /// pub mod name; /// pub mod state; /// pub mod search; /// pub mod parse; /// Parse attribute assignments line by line from `bytes`, and fail the operation on error. /// /// For leniency, ignore errors using `filter_map(Result::ok)` for example. pub fn parse(bytes: &[u8]) -> parse::Lines<'_> { parse::Lines::new(bytes) } /// The state an attribute can be in, referencing the value. /// /// Note that this doesn't contain the name. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum StateRef<'a> { /// The attribute is listed, or has the special value 'true' Set, /// The attribute has the special value 'false', or was prefixed with a `-` sign. Unset, /// The attribute is set to the given value, which followed the `=` sign. /// Note that values can be empty. #[cfg_attr(feature = "serde", serde(borrow))] Value(state::ValueRef<'a>), /// The attribute isn't mentioned with a given path or is explicitly set to `Unspecified` using the `!` sign. Unspecified, } /// The state an attribute can be in, owning the value. /// /// Note that this doesn't contain the name. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum State { /// The attribute is listed, or has the special value 'true' Set, /// The attribute has the special value 'false', or was prefixed with a `-` sign. Unset, /// The attribute is set to the given value, which followed the `=` sign. /// Note that values can be empty. Value(state::Value), /// The attribute isn't mentioned with a given path or is explicitly set to `Unspecified` using the `!` sign. Unspecified, } /// Represents a validated attribute name #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Name(pub(crate) KString); /// Holds a validated attribute name as a reference #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct NameRef<'a>(KStringRef<'a>); /// Name an attribute and describe it's assigned state. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Assignment { /// The validated name of the attribute. pub name: Name, /// The state of the attribute. pub state: State, } /// Holds validated attribute data as a reference #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct AssignmentRef<'a> { /// The name of the attribute. pub name: NameRef<'a>, /// The state of the attribute. pub state: StateRef<'a>, } /// A grouping of lists of patterns while possibly keeping associated to their base path in order to find matches. /// /// Pattern lists with base path are queryable relative to that base, otherwise they are relative to the repository root. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] pub struct Search { /// A list of pattern lists, each representing a patterns from a file or specified by hand, in the order they were /// specified in. /// /// When matching, this order is reversed. patterns: Vec>, } /// A list of known global sources for git attribute files in order of ascending precedence. /// /// This means that values from the first variant will be returned first. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Source { /// The attribute file that the installation itself ships with. GitInstallation, /// System-wide attributes file. This is typically defined as /// `$(prefix)/etc/gitattributes` (where prefix is the git-installation directory). System, /// This is `/git/attributes` and is git application configuration per user. /// /// Note that there is no `~/.gitattributes` file. Git, /// The configuration of the repository itself, located in `$GIT_DIR/info/attributes`. Local, } mod source; gix-attributes-0.23.1/src/name.rs000064400000000000000000000031201046102023000147330ustar 00000000000000use crate::{Name, NameRef}; use bstr::{BStr, BString, ByteSlice}; use kstring::KStringRef; impl NameRef<'_> { /// Turn this ref into its owned counterpart. pub fn to_owned(self) -> Name { Name(self.0.into()) } /// Return the inner `str`. pub fn as_str(&self) -> &str { self.0.as_str() } } impl AsRef for NameRef<'_> { fn as_ref(&self) -> &str { self.0.as_ref() } } impl<'a> TryFrom<&'a BStr> for NameRef<'a> { type Error = Error; fn try_from(attr: &'a BStr) -> Result { fn attr_valid(attr: &BStr) -> bool { if attr.first() == Some(&b'-') { return false; } attr.bytes() .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) } attr_valid(attr) .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) .ok_or_else(|| Error { attribute: attr.into() }) } } impl<'a> Name { /// Provide our ref-type. pub fn as_ref(&'a self) -> NameRef<'a> { NameRef(self.0.as_ref()) } /// Return the inner `str`. pub fn as_str(&self) -> &str { self.0.as_str() } } impl AsRef for Name { fn as_ref(&self) -> &str { self.0.as_str() } } /// The error returned by [`parse::Iter`][crate::parse::Iter]. #[derive(Debug, thiserror::Error)] #[error("Attribute has non-ascii characters or starts with '-': {attribute}")] pub struct Error { /// The attribute that failed to parse. pub attribute: BString, } gix-attributes-0.23.1/src/parse.rs000064400000000000000000000127731046102023000151430ustar 00000000000000use std::borrow::Cow; use crate::{name, AssignmentRef, Name, NameRef, StateRef}; use bstr::{BStr, ByteSlice}; use kstring::KStringRef; /// The kind of attribute that was parsed. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Kind { /// A pattern to match paths against Pattern(gix_glob::Pattern), /// The name of the macro to define, always a valid attribute name Macro(Name), } mod error { use bstr::BString; /// The error returned by [`parse::Lines`][crate::parse::Lines]. #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] pub enum Error { #[error("Line {line_number} has a negative pattern, for literal characters use \\!: {line}")] PatternNegation { line_number: usize, line: BString }, #[error("Attribute in line {line_number} has non-ascii characters or starts with '-': {attribute}")] AttributeName { line_number: usize, attribute: BString }, #[error("Macro in line {line_number} has non-ascii characters or starts with '-': {macro_name}")] MacroName { line_number: usize, macro_name: BString }, #[error("Could not unquote attributes line")] Unquote(#[from] gix_quote::ansi_c::undo::Error), } } pub use error::Error; /// An iterator over attribute assignments, parsed line by line. pub struct Lines<'a> { lines: bstr::Lines<'a>, line_no: usize, } /// An iterator over attribute assignments in a single line. pub struct Iter<'a> { attrs: bstr::Fields<'a>, } impl<'a> Iter<'a> { /// Create a new instance to parse attribute assignments from `input`. pub fn new(input: &'a BStr) -> Self { Iter { attrs: input.fields() } } fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { let mut tokens = attr.splitn(2, |b| *b == b'='); let attr = tokens.next().expect("attr itself").as_bstr(); let possibly_value = tokens.next(); let (attr, state) = if attr.first() == Some(&b'-') { (&attr[1..], StateRef::Unset) } else if attr.first() == Some(&b'!') { (&attr[1..], StateRef::Unspecified) } else { (attr, possibly_value.map_or(StateRef::Set, StateRef::from_bytes)) }; Ok(AssignmentRef::new(check_attr(attr)?, state)) } } fn check_attr(attr: &BStr) -> Result, name::Error> { fn attr_valid(attr: &BStr) -> bool { if attr.first() == Some(&b'-') { return false; } attr.bytes() .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) } attr_valid(attr) .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) .ok_or_else(|| name::Error { attribute: attr.into() }) } impl<'a> Iterator for Iter<'a> { type Item = Result, name::Error>; fn next(&mut self) -> Option { let attr = self.attrs.next().filter(|a| !a.is_empty())?; self.parse_attr(attr).into() } } /// Instantiation impl<'a> Lines<'a> { /// Create a new instance to parse all attributes in all lines of the input `bytes`. pub fn new(bytes: &'a [u8]) -> Self { let bom = unicode_bom::Bom::from(bytes); Lines { lines: bytes[bom.len()..].lines(), line_no: 0, } } } impl<'a> Iterator for Lines<'a> { type Item = Result<(Kind, Iter<'a>, usize), Error>; fn next(&mut self) -> Option { fn skip_blanks(line: &BStr) -> &BStr { line.find_not_byteset(BLANKS).map_or(line, |pos| &line[pos..]) } for line in self.lines.by_ref() { self.line_no += 1; let line = skip_blanks(line.into()); if line.first() == Some(&b'#') { continue; } match parse_line(line, self.line_no) { None => continue, Some(res) => return Some(res), } } None } } fn parse_line(line: &BStr, line_number: usize) -> Option, usize), Error>> { if line.is_empty() { return None; } let (line, attrs): (Cow<'_, _>, _) = if line.starts_with(b"\"") { let (unquoted, consumed) = match gix_quote::ansi_c::undo(line) { Ok(res) => res, Err(err) => return Some(Err(err.into())), }; (unquoted, &line[consumed..]) } else { line.find_byteset(BLANKS) .map(|pos| (line[..pos].as_bstr().into(), line[pos..].as_bstr())) .unwrap_or((line.into(), [].as_bstr())) }; let kind_res = match line.strip_prefix(b"[attr]") { Some(macro_name) => check_attr(macro_name.into()) .map_err(|err| Error::MacroName { line_number, macro_name: err.attribute, }) .map(|name| Kind::Macro(name.to_owned())), None => { let pattern = gix_glob::Pattern::from_bytes(line.as_ref())?; if pattern.mode.contains(gix_glob::pattern::Mode::NEGATIVE) { Err(Error::PatternNegation { line: line.into_owned(), line_number, }) } else { Ok(Kind::Pattern(pattern)) } } }; let kind = match kind_res { Ok(kind) => kind, Err(err) => return Some(Err(err)), }; Ok((kind, Iter::new(attrs), line_number)).into() } const BLANKS: &[u8] = b" \t\r"; gix-attributes-0.23.1/src/search/attributes.rs000064400000000000000000000216071046102023000174600ustar 00000000000000use std::path::{Path, PathBuf}; use bstr::{BStr, ByteSlice}; use gix_glob::search::{pattern, Pattern}; use super::Attributes; use crate::{ search::{Assignments, MetadataCollection, Outcome, TrackedAssignment, Value}, Search, }; /// Instantiation and initialization. impl Search { /// Create a search instance preloaded with *built-ins* followed by attribute `files` from various global locations. /// /// See [`Source`][crate::Source] for a way to obtain these paths. /// /// Note that parsing is lenient and errors are logged. /// /// * `buf` is used to read `files` from disk which will be ignored if they do not exist. /// * `collection` will be updated with information necessary to perform lookups later. pub fn new_globals( files: impl IntoIterator>, buf: &mut Vec, collection: &mut MetadataCollection, ) -> std::io::Result { let mut group = Self::default(); group.add_patterns_buffer( b"[attr]binary -diff -merge -text", "[builtin]".into(), None, collection, true, /* allow macros */ ); for path in files.into_iter() { group.add_patterns_file(path.into(), true, None, buf, collection, true /* allow macros */)?; } Ok(group) } } /// Mutation impl Search { /// Add the given file at `source` to our patterns if it exists, otherwise do nothing. /// Update `collection` with newly added attribute names. /// If a `root` is provided, it's not considered a global file anymore. /// If `allow_macros` is `true`, macros will be processed like normal, otherwise they will be skipped entirely. /// Returns `true` if the file was added, or `false` if it didn't exist. pub fn add_patterns_file( &mut self, source: PathBuf, follow_symlinks: bool, root: Option<&Path>, buf: &mut Vec, collection: &mut MetadataCollection, allow_macros: bool, ) -> std::io::Result { // TODO: should `Pattern` trait use an instance as first argument to carry this information // (so no `retain` later, it's slower than skipping) let was_added = gix_glob::search::add_patterns_file(&mut self.patterns, source, follow_symlinks, root, buf)?; if was_added { let last = self.patterns.last_mut().expect("just added"); if !allow_macros { last.patterns .retain(|p| !matches!(p.value, Value::MacroAssignments { .. })); } collection.update_from_list(last); } Ok(was_added) } /// Add patterns as parsed from `bytes`, providing their `source` path and possibly their `root` path, the path they /// are relative to. This also means that `source` is contained within `root` if `root` is provided. /// If `allow_macros` is `true`, macros will be processed like normal, otherwise they will be skipped entirely. pub fn add_patterns_buffer( &mut self, bytes: &[u8], source: PathBuf, root: Option<&Path>, collection: &mut MetadataCollection, allow_macros: bool, ) { self.patterns.push(pattern::List::from_bytes(bytes, source, root)); let last = self.patterns.last_mut().expect("just added"); if !allow_macros { last.patterns .retain(|p| !matches!(p.value, Value::MacroAssignments { .. })); } collection.update_from_list(last); } /// Pop the last attribute patterns list from our queue. pub fn pop_pattern_list(&mut self) -> Option> { self.patterns.pop() } } /// Access and matching impl Search { /// Match `relative_path`, a path relative to the repository, while respective `case`-sensitivity and write them to `out` /// Return `true` if at least one pattern matched. pub fn pattern_matching_relative_path( &self, relative_path: &BStr, case: gix_glob::pattern::Case, is_dir: Option, out: &mut Outcome, ) -> bool { let basename_pos = relative_path.rfind(b"/").map(|p| p + 1); let mut has_match = false; self.patterns.iter().rev().any(|pl| { has_match |= pattern_matching_relative_path(pl, relative_path, basename_pos, case, is_dir, out); out.is_done() }); has_match } /// Return the amount of pattern lists contained in this instance. pub fn num_pattern_lists(&self) -> usize { self.patterns.len() } } impl Pattern for Attributes { type Value = Value; fn bytes_to_patterns(bytes: &[u8], _source: &std::path::Path) -> Vec> { fn into_owned_assignments<'a>( attrs: impl Iterator, crate::name::Error>>, ) -> Option { let res = attrs .map(|res| { res.map(|a| TrackedAssignment { id: Default::default(), inner: a.to_owned(), }) }) .collect::>(); match res { Ok(res) => Some(res), Err(_err) => { gix_trace::warn!("{}", _err); None } } } crate::parse(bytes) .filter_map(|res| match res { Ok(pattern) => Some(pattern), Err(_err) => { gix_trace::warn!("{}: {}", _source.display(), _err); None } }) .filter_map(|(pattern_kind, assignments, line_number)| { let (pattern, value) = match pattern_kind { crate::parse::Kind::Macro(macro_name) => ( gix_glob::Pattern { text: macro_name.as_str().into(), mode: macro_mode(), first_wildcard_pos: None, }, Value::MacroAssignments { id: Default::default(), assignments: into_owned_assignments(assignments)?, }, ), crate::parse::Kind::Pattern(p) => ( (!p.is_negative()).then_some(p)?, Value::Assignments(into_owned_assignments(assignments)?), ), }; pattern::Mapping { pattern, value, sequence_number: line_number, } .into() }) .collect() } } impl Attributes { fn may_use_glob_pattern(pattern: &gix_glob::Pattern) -> bool { pattern.mode != macro_mode() } } fn macro_mode() -> gix_glob::pattern::Mode { gix_glob::pattern::Mode::all() } /// Append all matches of patterns matching `relative_path` to `out`, /// providing a pre-computed `basename_pos` which is the starting position of the basename of `relative_path`. /// `case` specifies whether cases should be folded during matching or not. /// `is_dir` is true if `relative_path` is a directory. /// Return `true` if at least one pattern matched. #[allow(unused_variables)] fn pattern_matching_relative_path( list: &gix_glob::search::pattern::List, relative_path: &BStr, basename_pos: Option, case: gix_glob::pattern::Case, is_dir: Option, out: &mut Outcome, ) -> bool { let (relative_path, basename_start_pos) = match list.strip_base_handle_recompute_basename_pos(relative_path, basename_pos, case) { Some(r) => r, None => return false, }; let cur_len = out.remaining(); 'outer: for pattern::Mapping { pattern, value, sequence_number, } in list .patterns .iter() .rev() .filter(|pm| Attributes::may_use_glob_pattern(&pm.pattern)) { let value: &Value = value; let attrs = match value { Value::MacroAssignments { .. } => { unreachable!("we can't match on macros as they have no pattern") } Value::Assignments(attrs) => attrs, }; if out.has_unspecified_attributes(attrs.iter().map(|attr| attr.id)) && pattern.matches_repo_relative_path( relative_path, basename_start_pos, is_dir, case, gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, ) { let all_filled = out.fill_attributes(attrs.iter(), pattern, list.source.as_ref(), *sequence_number); if all_filled { break 'outer; } } } cur_len != out.remaining() } gix-attributes-0.23.1/src/search/mod.rs000064400000000000000000000146431046102023000160530ustar 00000000000000use kstring::KString; use smallvec::SmallVec; use std::collections::HashMap; use crate::{Assignment, AssignmentRef}; mod attributes; mod outcome; mod refmap; pub(crate) use refmap::RefMap; /// A typically sized list of attributes. pub type Assignments = SmallVec<[TrackedAssignment; AVERAGE_NUM_ATTRS]>; /// A value of a [pattern mapping][gix_glob::search::pattern::Mapping], /// which is either a macro definition or a set of attributes. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Value { /// A macro, whose name resolves to the contained assignments. Note that the name is the pattern of the mapping itself. MacroAssignments { /// The id of the macro itself, which is both an attribute as well as a set of additional attributes into which the macro /// resolves id: AttributeId, /// The attributes or assignments that the macro resolves to. assignments: Assignments, }, /// A set of assignments which are the attributes themselves. Assignments(Assignments), } /// A way to have an assignment (`attr=value`) but also associated it with an id that allows perfect mapping /// to tracking information. /// Note that the order is produced after the files are parsed as global ordering is needed that goes beyond the scope of a /// single `Search` instance. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct TrackedAssignment { /// The order of the assignment. pub id: AttributeId, /// The actual assignment information. pub inner: Assignment, } /// An implementation of the [`Pattern`][gix_glob::search::Pattern] trait for attributes. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] pub struct Attributes; /// Describes a matching pattern with #[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct Match<'a> { /// The glob pattern itself, like `/target/*`. pub pattern: &'a gix_glob::Pattern, /// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern. pub assignment: AssignmentRef<'a>, /// Additional information about the kind of match. pub kind: MatchKind, /// Information about the location of the match. pub location: MatchLocation<'a>, } /// Describes in which what file and line the match was found. #[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct MatchLocation<'a> { /// The path to the source from which the pattern was loaded, or `None` if it was specified by other means. pub source: Option<&'a std::path::Path>, /// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided. pub sequence_number: usize, } /// The kind of attribute within the context of a [match][Match]. #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub enum MatchKind { /// A attribute. Attribute { /// The location of the macro which referred to it the list with all in-order attributes and macros, or `None` if /// this is attribute wasn't resolved. /// /// Use [`Outcome::match_by_id()`] to retrieve the macro. macro_id: Option, }, /// The attribute is a macro, which will resolve into one or more attributes or macros. Macro { /// The location of the parent macro which referred to this one in the list with all in-order attributes and macros, /// or `None` if this is macro wasn't resolved by another one. /// /// Use [`Outcome::match_by_id()`] to retrieve the parent. parent_macro_id: Option, }, } /// The result of a search, containing all matching attributes. #[derive(Default, Clone)] pub struct Outcome { /// The list of all available attributes, by ascending order. Each slots index corresponds to an attribute with that order, i.e. /// `arr[attr.id] = `. /// /// This list needs to be up-to-date with the search group so all possible attribute names are known. matches_by_id: Vec, /// A stack of attributes to use for processing attributes of matched patterns and for resolving their macros. attrs_stack: SmallVec<[(AttributeId, Assignment, Option); 8]>, /// A set of attributes we should limit ourselves to, or empty if we should fill in all attributes, made of selected: SmallVec<[(KString, Option); AVERAGE_NUM_ATTRS]>, /// storage for all patterns we have matched so far (in order to avoid referencing them, we copy them, but only once). patterns: RefMap, /// storage for all assignments we have matched so far (in order to avoid referencing them, we copy them, but only once). assignments: RefMap, /// storage for all source paths we have matched so far (in order to avoid referencing them, we copy them, but only once). source_paths: RefMap, /// The amount of attributes that still need to be set, or `None` if this outcome is consumed which means it /// needs to be re-initialized. remaining: Option, } #[derive(Default, Clone)] struct Slot { r#match: Option, /// A list of all assignments, being an empty list for non-macro attributes, or all assignments (with order) for macros. /// It's used to resolve macros. macro_attributes: Assignments, } /// A type to denote an id of an attribute assignment for uniquely identifying each attribute or assignment. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] pub struct AttributeId(pub usize); impl Default for AttributeId { fn default() -> Self { AttributeId(usize::MAX) } } /// A utility type to collect metadata for each attribute, unified by its name. #[derive(Clone, Debug, Default)] pub struct MetadataCollection { /// A mapping of an attribute or macro name to its order, that is the time when it was *first* seen. /// /// This is the inverse of the order attributes are searched. name_to_meta: HashMap, } /// Metadata associated with an attribute or macro name. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Metadata { /// The id to uniquely identify an attribute in the [MetadataCollection]. pub id: AttributeId, /// If non-zero in length, this entry belongs to a macro which resolves to these attribute names. pub macro_attributes: Assignments, } const AVERAGE_NUM_ATTRS: usize = 3; gix-attributes-0.23.1/src/search/outcome.rs000064400000000000000000000374661046102023000167570ustar 00000000000000use 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.clone_from(macro_attributes); } 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>>, ) { 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>, ) { 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.initialize(collection); 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.clone_from(&self.matches_by_id); 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> { 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> { 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> { 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, 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) -> 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) { 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 { 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 .clone_from(attrs); 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 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 { 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, /// 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, } } } gix-attributes-0.23.1/src/search/refmap.rs000064400000000000000000000031041046102023000165340ustar 00000000000000//! A utility to store objects by identity, which deduplicates them while avoiding lifetimes. //! //! We chose to use hashing/identity over pointers as it's possible that different objects end up in the same memory location, //! which would create obscure bugs. The same could happen with hash collisions, but they these are designed to be less likely. use std::{ collections::{btree_map::Entry, hash_map::DefaultHasher, BTreeMap}, hash::{Hash, Hasher}, }; pub(crate) type RefMapKey = u64; #[derive(Clone)] pub(crate) struct RefMap(BTreeMap); impl Default for RefMap { fn default() -> Self { RefMap(Default::default()) } } impl RefMap where T: Hash + Clone, { pub(crate) fn len(&self) -> usize { self.0.len() } pub(crate) fn insert(&mut self, value: &T) -> RefMapKey { let mut s = DefaultHasher::new(); value.hash(&mut s); let key = s.finish(); match self.0.entry(key) { Entry::Vacant(e) => { e.insert(value.clone()); key } Entry::Occupied(_) => key, } } pub(crate) fn insert_owned(&mut self, value: T) -> RefMapKey { let mut s = DefaultHasher::new(); value.hash(&mut s); let key = s.finish(); match self.0.entry(key) { Entry::Vacant(e) => { e.insert(value); key } Entry::Occupied(_) => key, } } pub(crate) fn resolve(&self, key: RefMapKey) -> Option<&T> { self.0.get(&key) } } gix-attributes-0.23.1/src/source.rs000064400000000000000000000021641046102023000153220ustar 00000000000000use std::{borrow::Cow, ffi::OsString, path::Path}; use crate::Source; impl Source { /// Produce a storage location for the this source while potentially querying environment variables using `env_var()`, /// or `None` if the storage location could not be obtained. /// /// Note that local sources are returned as relative paths to be joined with the base in a separate step. pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option) -> Option> { use Source::*; Some(match self { GitInstallation => gix_path::env::installation_config_prefix()? .join("gitattributes") .into(), System => { if env_var("GIT_ATTR_NOSYSTEM").is_some() { return None; } else { gix_path::env::system_prefix()?.join("etc/gitattributes").into() } } Git => return gix_path::env::xdg_config("attributes", env_var).map(Cow::Owned), Local => Cow::Borrowed(Path::new("info/attributes")), }) } } gix-attributes-0.23.1/src/state.rs000064400000000000000000000075151046102023000151470ustar 00000000000000use crate::{State, StateRef}; use bstr::{BStr, BString, ByteSlice}; /// A container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] // TODO: This should be some sort of 'smallbstring' - but can't use `kstring` here due to UTF8 requirement. 5% performance boost possible. // What's really needed here is a representation that displays as string when serialized which helps with JSON. // Maybe `smallvec` with display and serialization wrapper would do the trick? pub struct Value(BString); /// A reference container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ValueRef<'a>(#[cfg_attr(feature = "serde", serde(borrow))] &'a [u8]); /// Lifecycle impl<'a> ValueRef<'a> { /// Keep `input` as our value. pub fn from_bytes(input: &'a [u8]) -> Self { Self(input) } } /// Access and conversions impl<'a> ValueRef<'a> { /// Access this value as byte string. pub fn as_bstr(&self) -> &'a BStr { self.0.as_bytes().as_bstr() } /// Convert this instance into its owned form. pub fn to_owned(self) -> Value { self.into() } } impl<'a> From<&'a str> for ValueRef<'a> { fn from(v: &'a str) -> Self { ValueRef(v.as_bytes()) } } impl<'a> From> for Value { fn from(v: ValueRef<'a>) -> Self { Value(v.0.into()) } } impl From<&str> for Value { fn from(v: &str) -> Self { Value(v.as_bytes().into()) } } /// Access impl Value { /// Return ourselves as reference. pub fn as_ref(&self) -> ValueRef<'_> { ValueRef(self.0.as_ref()) } } /// Access impl StateRef<'_> { /// Return `true` if the associated attribute was set to be unspecified using the `!attr` prefix or it wasn't mentioned. pub fn is_unspecified(&self) -> bool { matches!(self, StateRef::Unspecified) } /// Return `true` if the associated attribute was set with `attr`. Note that this will also be `true` if a value is assigned. pub fn is_set(&self) -> bool { matches!(self, StateRef::Set | StateRef::Value(_)) } /// Return `true` if the associated attribute was set with `-attr` to specifically remove it. pub fn is_unset(&self) -> bool { matches!(self, StateRef::Unset) } /// Attempt to obtain the string value of this state, or return `None` if there is no such value. pub fn as_bstr(&self) -> Option<&BStr> { match self { StateRef::Value(v) => Some(v.as_bstr()), _ => None, } } } /// Initialization impl<'a> StateRef<'a> { /// Keep `input` in one of our enums. pub fn from_bytes(input: &'a [u8]) -> Self { Self::Value(ValueRef::from_bytes(input)) } } /// Access impl StateRef<'_> { /// Turn ourselves into our owned counterpart. pub fn to_owned(self) -> State { self.into() } } impl<'a> State { /// Turn ourselves into our ref-type. pub fn as_ref(&'a self) -> StateRef<'a> { match self { State::Value(v) => StateRef::Value(v.as_ref()), State::Set => StateRef::Set, State::Unset => StateRef::Unset, State::Unspecified => StateRef::Unspecified, } } } impl<'a> From> for State { fn from(s: StateRef<'a>) -> Self { match s { StateRef::Value(v) => State::Value(v.into()), StateRef::Set => State::Set, StateRef::Unset => State::Unset, StateRef::Unspecified => State::Unspecified, } } }