Skip to content

Commit

Permalink
Add docs for git-attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Jul 24, 2022
1 parent 1cbc142 commit 0eabea9
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ is usable to some extend.
* [git-discover](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-discover)
* [git-path](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-path)
* [git-repository](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-repository)
* [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes)
* `gitoxide-core`
* **very early** _(possibly without any documentation and many rough edges)_
* [git-index](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-index)
* [git-worktree](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-worktree)
* [git-bitmap](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-bitmap)
* [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes)
* [git-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec)
* [git-revision](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-revision)
* [git-date](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-date)
Expand Down
2 changes: 2 additions & 0 deletions git-attributes/src/assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ impl<'a> AssignmentRef<'a> {
AssignmentRef { name, state }
}

/// Turn this reference into its owned counterpart.
pub fn to_owned(self) -> Assignment {
self.into()
}
Expand All @@ -20,6 +21,7 @@ impl<'a> From<AssignmentRef<'a>> for Assignment {
}

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())
}
Expand Down
15 changes: 12 additions & 3 deletions git-attributes/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Parse `.gitattribute` and `.gitignore` files and provide utilities to match against them.
//!
//! ## Feature Flags
#![cfg_attr(
feature = "document-features",
cfg_attr(doc, doc = ::document_features::document_features!())
)]
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms)]
#![deny(rust_2018_idioms, missing_docs)]

use std::path::PathBuf;

Expand All @@ -13,15 +15,18 @@ use compact_str::CompactString;
pub use git_glob as glob;

mod assignment;
///
pub mod name;
mod state;

mod match_group;
pub use match_group::{Attributes, Ignore, Match, Pattern};

///
pub mod parse;
pub fn parse(buf: &[u8]) -> parse::Lines<'_> {
parse::Lines::new(buf)
/// Parse attribute assignments line by line from `bytes`.
pub fn parse(bytes: &[u8]) -> parse::Lines<'_> {
parse::Lines::new(bytes)
}

/// The state an attribute can be in, referencing the value.
Expand Down Expand Up @@ -120,9 +125,13 @@ pub struct PatternList<T: Pattern> {
pub base: Option<BString>,
}

/// An association of a pattern with its value, along with a sequence number providing a sort order in relation to its peers.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub struct PatternMapping<T> {
/// The pattern itself, like `/target/*`
pub pattern: git_glob::Pattern,
/// The value associated with the pattern.
pub value: T,
/// Typically the line number in the file the pattern was parsed from.
pub sequence_number: usize,
}
40 changes: 27 additions & 13 deletions git-attributes/src/match_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,27 @@ use std::{
path::{Path, PathBuf},
};

fn attrs_to_assignments<'a>(
fn into_owned_assignments<'a>(
attrs: impl Iterator<Item = Result<crate::AssignmentRef<'a>, crate::name::Error>>,
) -> Result<Vec<Assignment>, crate::name::Error> {
attrs.map(|res| res.map(|attr| attr.to_owned())).collect()
}

/// A marker trait to identify the type of a description.
/// A trait to convert bytes into patterns and their associated value.
///
/// This is used for `gitattributes` which have a value, and `gitignore` which don't.
pub trait Pattern: Clone + PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Default {
/// The value associated with a pattern.
type Value: PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Clone;

/// Parse all patterns in `bytes` line by line, ignoring lines with errors, and collect them.
fn bytes_to_patterns(bytes: &[u8]) -> Vec<PatternMapping<Self::Value>>;

fn use_pattern(pattern: &git_glob::Pattern) -> bool;
/// Returns true if the given pattern may be used for matching.
fn may_use_glob_pattern(pattern: &git_glob::Pattern) -> bool;
}

/// Identify ignore patterns.
/// An implementation of the [`Pattern`] trait for ignore patterns.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)]
pub struct Ignore;

Expand All @@ -40,7 +43,7 @@ impl Pattern for Ignore {
.collect()
}

fn use_pattern(_pattern: &git_glob::Pattern) -> bool {
fn may_use_glob_pattern(_pattern: &git_glob::Pattern) -> bool {
true
}
}
Expand All @@ -49,10 +52,10 @@ impl Pattern for Ignore {
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub enum Value {
MacroAttributes(Vec<Assignment>),
Attributes(Vec<Assignment>),
Assignments(Vec<Assignment>),
}

/// Identify patterns with attributes.
/// An implementation of the [`Pattern`] trait for attributes.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)]
pub struct Attributes;

Expand All @@ -62,19 +65,19 @@ impl Pattern for Attributes {
fn bytes_to_patterns(bytes: &[u8]) -> Vec<PatternMapping<Self::Value>> {
crate::parse(bytes)
.filter_map(Result::ok)
.filter_map(|(pattern_kind, attrs, line_number)| {
.filter_map(|(pattern_kind, assignments, line_number)| {
let (pattern, value) = match pattern_kind {
crate::parse::Kind::Macro(macro_name) => (
git_glob::Pattern {
text: macro_name.as_str().into(),
mode: git_glob::pattern::Mode::all(),
first_wildcard_pos: None,
},
Value::MacroAttributes(attrs_to_assignments(attrs).ok()?),
Value::MacroAttributes(into_owned_assignments(assignments).ok()?),
),
crate::parse::Kind::Pattern(p) => (
(!p.is_negative()).then(|| p)?,
Value::Attributes(attrs_to_assignments(attrs).ok()?),
Value::Assignments(into_owned_assignments(assignments).ok()?),
),
};
PatternMapping {
Expand All @@ -87,14 +90,15 @@ impl Pattern for Attributes {
.collect()
}

fn use_pattern(pattern: &git_glob::Pattern) -> bool {
fn may_use_glob_pattern(pattern: &git_glob::Pattern) -> bool {
pattern.mode != git_glob::pattern::Mode::all()
}
}

/// Describes a matching value within a [`MatchGroup`].
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub struct Match<'a, T> {
/// The glob pattern itself, like `/target/*`.
pub pattern: &'a git_glob::Pattern,
/// The value associated with the pattern.
pub value: &'a T,
Expand Down Expand Up @@ -179,6 +183,8 @@ impl MatchGroup<Ignore> {
Ok(self.patterns.len() != previous_len)
}

/// 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.
pub fn add_patterns_buffer(&mut self, bytes: &[u8], source: impl Into<PathBuf>, root: Option<&Path>) {
self.patterns
.push(PatternList::<Ignore>::from_bytes(bytes, source.into(), root));
Expand Down Expand Up @@ -228,6 +234,9 @@ where
base,
}
}

/// Create a pattern list from the `source` file, which may be located underneath `root`, while optionally
/// following symlinks with `follow_symlinks`, providing `buf` to temporarily store the data contained in the file.
pub fn from_file(
source: impl Into<PathBuf>,
root: Option<&Path>,
Expand All @@ -243,6 +252,9 @@ impl<T> PatternList<T>
where
T: Pattern,
{
/// Return a match if a pattern matches `relative_path`, providing a pre-computed `basename_pos` which is the
/// starting position of the basename of `relative_path`. `is_dir` is true if `relative_path` is a directory.
/// `case` specifies whether cases should be folded during matching or not.
pub fn pattern_matching_relative_path(
&self,
relative_path: &BStr,
Expand All @@ -255,7 +267,7 @@ where
self.patterns
.iter()
.rev()
.filter(|pm| T::use_pattern(&pm.pattern))
.filter(|pm| T::may_use_glob_pattern(&pm.pattern))
.find_map(
|PatternMapping {
pattern,
Expand All @@ -274,6 +286,8 @@ where
)
}

/// Like [`pattern_matching_relative_path()`][Self::pattern_matching_relative_path()], but returns an index to the pattern
/// that matched `relative_path`, instead of the match itself.
pub fn pattern_idx_matching_relative_path(
&self,
relative_path: &BStr,
Expand All @@ -287,7 +301,7 @@ where
.iter()
.enumerate()
.rev()
.filter(|(_, pm)| T::use_pattern(&pm.pattern))
.filter(|(_, pm)| T::may_use_glob_pattern(&pm.pattern))
.find_map(|(idx, pm)| {
pm.pattern
.matches_repo_relative_path(relative_path, basename_start_pos, is_dir, case)
Expand Down
6 changes: 6 additions & 0 deletions git-attributes/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use crate::{Name, NameRef};
use bstr::BString;

impl<'a> NameRef<'a> {
/// 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
}
Expand All @@ -18,10 +20,12 @@ impl AsRef<str> for NameRef<'_> {
}

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()
}
Expand All @@ -33,8 +37,10 @@ impl AsRef<str> for Name {
}
}

/// 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,
}
17 changes: 12 additions & 5 deletions git-attributes/src/parse/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{name, AssignmentRef, Name, NameRef, StateRef};
use bstr::{BStr, ByteSlice};
use std::borrow::Cow;

/// The kind of attribute that was parsed.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Kind {
Expand All @@ -13,7 +14,9 @@ pub enum Kind {

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 },
Expand All @@ -27,18 +30,21 @@ mod 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> {
pub fn new(attrs: &'a BStr) -> Self {
Iter { attrs: attrs.fields() }
/// 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<AssignmentRef<'a>, name::Error> {
Expand Down Expand Up @@ -86,10 +92,11 @@ impl<'a> Iterator for Iter<'a> {
}

impl<'a> Lines<'a> {
pub fn new(buf: &'a [u8]) -> Self {
let bom = unicode_bom::Bom::from(buf);
/// 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: buf[bom.len()..].lines(),
lines: bytes[bom.len()..].lines(),
line_no: 0,
}
}
Expand Down
2 changes: 2 additions & 0 deletions git-attributes/src/parse/ignore.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use bstr::ByteSlice;

/// An iterator over line-wise ignore patterns parsed from a buffer.
pub struct Lines<'a> {
lines: bstr::Lines<'a>,
line_no: usize,
}

impl<'a> Lines<'a> {
/// Create a new instance from `buf` to parse ignore patterns from.
pub fn new(buf: &'a [u8]) -> Self {
let bom = unicode_bom::Bom::from(buf);
Lines {
Expand Down
6 changes: 4 additions & 2 deletions git-attributes/src/parse/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
///
pub mod ignore;

mod attribute;
pub use attribute::{Error, Iter, Kind, Lines};

pub fn ignore(buf: &[u8]) -> ignore::Lines<'_> {
ignore::Lines::new(buf)
/// Parse git ignore patterns, line by line, from `bytes`.
pub fn ignore(bytes: &[u8]) -> ignore::Lines<'_> {
ignore::Lines::new(bytes)
}
2 changes: 2 additions & 0 deletions git-attributes/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use crate::{State, StateRef};
use bstr::ByteSlice;

impl<'a> StateRef<'a> {
/// 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_bytes().as_bstr()),
Expand Down

0 comments on commit 0eabea9

Please sign in to comment.