/src/gitoxide/gix-attributes/src/parse.rs
Line | Count | Source |
1 | | use std::borrow::Cow; |
2 | | |
3 | | use bstr::{BStr, ByteSlice}; |
4 | | use kstring::KStringRef; |
5 | | |
6 | | use crate::{name, AssignmentRef, Name, NameRef, StateRef}; |
7 | | |
8 | | /// The kind of attribute that was parsed. |
9 | | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] |
10 | | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
11 | | pub enum Kind { |
12 | | /// A pattern to match paths against |
13 | | Pattern(gix_glob::Pattern), |
14 | | /// The name of the macro to define, always a valid attribute name |
15 | | Macro(Name), |
16 | | } |
17 | | |
18 | | mod error { |
19 | | use bstr::BString; |
20 | | /// The error returned by [`parse::Lines`][crate::parse::Lines]. |
21 | | #[derive(thiserror::Error, Debug)] |
22 | | #[allow(missing_docs)] |
23 | | pub enum Error { |
24 | | #[error(r"Line {line_number} has a negative pattern, for literal characters use \!: {line}")] |
25 | | PatternNegation { line_number: usize, line: BString }, |
26 | | #[error("Attribute in line {line_number} has non-ascii characters or starts with '-': {attribute}")] |
27 | | AttributeName { line_number: usize, attribute: BString }, |
28 | | #[error("Macro in line {line_number} has non-ascii characters or starts with '-': {macro_name}")] |
29 | | MacroName { line_number: usize, macro_name: BString }, |
30 | | #[error("Could not unquote attributes line")] |
31 | | Unquote(#[from] gix_quote::ansi_c::undo::Error), |
32 | | } |
33 | | } |
34 | | pub use error::Error; |
35 | | |
36 | | /// An iterator over attribute assignments, parsed line by line. |
37 | | pub struct Lines<'a> { |
38 | | lines: bstr::Lines<'a>, |
39 | | line_no: usize, |
40 | | } |
41 | | |
42 | | /// An iterator over attribute assignments in a single line. |
43 | | pub struct Iter<'a> { |
44 | | attrs: bstr::Fields<'a>, |
45 | | } |
46 | | |
47 | | impl<'a> Iter<'a> { |
48 | | /// Create a new instance to parse attribute assignments from `input`. |
49 | 2.07M | pub fn new(input: &'a BStr) -> Self { |
50 | 2.07M | Iter { attrs: input.fields() } |
51 | 2.07M | } Unexecuted instantiation: <gix_attributes::parse::Iter>::new <gix_attributes::parse::Iter>::new Line | Count | Source | 49 | 2.06M | pub fn new(input: &'a BStr) -> Self { | 50 | 2.06M | Iter { attrs: input.fields() } | 51 | 2.06M | } |
<gix_attributes::parse::Iter>::new Line | Count | Source | 49 | 2.97k | pub fn new(input: &'a BStr) -> Self { | 50 | 2.97k | Iter { attrs: input.fields() } | 51 | 2.97k | } |
|
52 | | |
53 | 13.6M | fn parse_attr(&self, attr: &'a [u8]) -> Result<AssignmentRef<'a>, name::Error> { |
54 | 57.6M | let mut tokens = attr.splitn(2, |b| *b == b'='); Unexecuted instantiation: <gix_attributes::parse::Iter>::parse_attr::{closure#0}<gix_attributes::parse::Iter>::parse_attr::{closure#0}Line | Count | Source | 54 | 26.2M | let mut tokens = attr.splitn(2, |b| *b == b'='); |
<gix_attributes::parse::Iter>::parse_attr::{closure#0}Line | Count | Source | 54 | 31.3M | let mut tokens = attr.splitn(2, |b| *b == b'='); |
|
55 | 13.6M | let attr = tokens.next().expect("attr itself").as_bstr(); |
56 | 13.6M | let possibly_value = tokens.next(); |
57 | 13.6M | let (attr, state) = if attr.first() == Some(&b'-') { |
58 | 22.9k | (&attr[1..], StateRef::Unset) |
59 | 13.6M | } else if attr.first() == Some(&b'!') { |
60 | 25.7k | (&attr[1..], StateRef::Unspecified) |
61 | | } else { |
62 | 13.6M | (attr, possibly_value.map_or(StateRef::Set, StateRef::from_bytes)) |
63 | | }; |
64 | 13.6M | Ok(AssignmentRef::new(check_attr(attr)?, state)) |
65 | 13.6M | } Unexecuted instantiation: <gix_attributes::parse::Iter>::parse_attr <gix_attributes::parse::Iter>::parse_attr Line | Count | Source | 53 | 7.03M | fn parse_attr(&self, attr: &'a [u8]) -> Result<AssignmentRef<'a>, name::Error> { | 54 | 7.03M | let mut tokens = attr.splitn(2, |b| *b == b'='); | 55 | 7.03M | let attr = tokens.next().expect("attr itself").as_bstr(); | 56 | 7.03M | let possibly_value = tokens.next(); | 57 | 7.03M | let (attr, state) = if attr.first() == Some(&b'-') { | 58 | 6.55k | (&attr[1..], StateRef::Unset) | 59 | 7.02M | } else if attr.first() == Some(&b'!') { | 60 | 22.1k | (&attr[1..], StateRef::Unspecified) | 61 | | } else { | 62 | 7.00M | (attr, possibly_value.map_or(StateRef::Set, StateRef::from_bytes)) | 63 | | }; | 64 | 7.03M | Ok(AssignmentRef::new(check_attr(attr)?, state)) | 65 | 7.03M | } |
<gix_attributes::parse::Iter>::parse_attr Line | Count | Source | 53 | 6.63M | fn parse_attr(&self, attr: &'a [u8]) -> Result<AssignmentRef<'a>, name::Error> { | 54 | 6.63M | let mut tokens = attr.splitn(2, |b| *b == b'='); | 55 | 6.63M | let attr = tokens.next().expect("attr itself").as_bstr(); | 56 | 6.63M | let possibly_value = tokens.next(); | 57 | 6.63M | let (attr, state) = if attr.first() == Some(&b'-') { | 58 | 16.3k | (&attr[1..], StateRef::Unset) | 59 | 6.61M | } else if attr.first() == Some(&b'!') { | 60 | 3.68k | (&attr[1..], StateRef::Unspecified) | 61 | | } else { | 62 | 6.60M | (attr, possibly_value.map_or(StateRef::Set, StateRef::from_bytes)) | 63 | | }; | 64 | 6.63M | Ok(AssignmentRef::new(check_attr(attr)?, state)) | 65 | 6.63M | } |
|
66 | | } |
67 | | |
68 | 13.6M | fn check_attr(attr: &BStr) -> Result<NameRef<'_>, name::Error> { |
69 | 13.6M | fn attr_valid(attr: &BStr) -> bool { |
70 | 13.6M | if attr.first() == Some(&b'-') { |
71 | 695 | return false; |
72 | 13.6M | } |
73 | | |
74 | 13.6M | attr.bytes() |
75 | 43.8M | .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) Unexecuted instantiation: gix_attributes::parse::check_attr::attr_valid::{closure#0}gix_attributes::parse::check_attr::attr_valid::{closure#0}Line | Count | Source | 75 | 19.3M | .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) |
gix_attributes::parse::check_attr::attr_valid::{closure#0}Line | Count | Source | 75 | 24.4M | .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) |
|
76 | 13.6M | } Unexecuted instantiation: gix_attributes::parse::check_attr::attr_valid gix_attributes::parse::check_attr::attr_valid Line | Count | Source | 69 | 7.05M | fn attr_valid(attr: &BStr) -> bool { | 70 | 7.05M | if attr.first() == Some(&b'-') { | 71 | 689 | return false; | 72 | 7.05M | } | 73 | | | 74 | 7.05M | attr.bytes() | 75 | 7.05M | .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) | 76 | 7.05M | } |
gix_attributes::parse::check_attr::attr_valid Line | Count | Source | 69 | 6.63M | fn attr_valid(attr: &BStr) -> bool { | 70 | 6.63M | if attr.first() == Some(&b'-') { | 71 | 6 | return false; | 72 | 6.63M | } | 73 | | | 74 | 6.63M | attr.bytes() | 75 | 6.63M | .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) | 76 | 6.63M | } |
|
77 | | |
78 | 13.6M | attr_valid(attr) |
79 | 13.6M | .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8"))))Unexecuted instantiation: gix_attributes::parse::check_attr::{closure#0}gix_attributes::parse::check_attr::{closure#0}Line | Count | Source | 79 | 7.01M | .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) |
gix_attributes::parse::check_attr::{closure#0}Line | Count | Source | 79 | 6.62M | .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) |
|
80 | 13.6M | .ok_or_else(|| name::Error { attribute: attr.into() })Unexecuted instantiation: gix_attributes::parse::check_attr::{closure#1}gix_attributes::parse::check_attr::{closure#1}Line | Count | Source | 80 | 36.1k | .ok_or_else(|| name::Error { attribute: attr.into() }) |
gix_attributes::parse::check_attr::{closure#1}Line | Count | Source | 80 | 684 | .ok_or_else(|| name::Error { attribute: attr.into() }) |
|
81 | 13.6M | } Unexecuted instantiation: gix_attributes::parse::check_attr gix_attributes::parse::check_attr Line | Count | Source | 68 | 7.05M | fn check_attr(attr: &BStr) -> Result<NameRef<'_>, name::Error> { | 69 | | fn attr_valid(attr: &BStr) -> bool { | 70 | | if attr.first() == Some(&b'-') { | 71 | | return false; | 72 | | } | 73 | | | 74 | | attr.bytes() | 75 | | .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) | 76 | | } | 77 | | | 78 | 7.05M | attr_valid(attr) | 79 | 7.05M | .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) | 80 | 7.05M | .ok_or_else(|| name::Error { attribute: attr.into() }) | 81 | 7.05M | } |
gix_attributes::parse::check_attr Line | Count | Source | 68 | 6.63M | fn check_attr(attr: &BStr) -> Result<NameRef<'_>, name::Error> { | 69 | | fn attr_valid(attr: &BStr) -> bool { | 70 | | if attr.first() == Some(&b'-') { | 71 | | return false; | 72 | | } | 73 | | | 74 | | attr.bytes() | 75 | | .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) | 76 | | } | 77 | | | 78 | 6.63M | attr_valid(attr) | 79 | 6.63M | .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) | 80 | 6.63M | .ok_or_else(|| name::Error { attribute: attr.into() }) | 81 | 6.63M | } |
|
82 | | |
83 | | impl<'a> Iterator for Iter<'a> { |
84 | | type Item = Result<AssignmentRef<'a>, name::Error>; |
85 | | |
86 | 15.6M | fn next(&mut self) -> Option<Self::Item> { |
87 | 15.6M | let attr = self.attrs.next().filter(|a| !a.is_empty())?; Unexecuted instantiation: <gix_attributes::parse::Iter as core::iter::traits::iterator::Iterator>::next::{closure#0}<gix_attributes::parse::Iter as core::iter::traits::iterator::Iterator>::next::{closure#0}Line | Count | Source | 87 | 7.03M | let attr = self.attrs.next().filter(|a| !a.is_empty())?; |
<gix_attributes::parse::Iter as core::iter::traits::iterator::Iterator>::next::{closure#0}Line | Count | Source | 87 | 6.63M | let attr = self.attrs.next().filter(|a| !a.is_empty())?; |
|
88 | 13.6M | self.parse_attr(attr).into() |
89 | 15.6M | } Unexecuted instantiation: <gix_attributes::parse::Iter as core::iter::traits::iterator::Iterator>::next <gix_attributes::parse::Iter as core::iter::traits::iterator::Iterator>::next Line | Count | Source | 86 | 9.06M | fn next(&mut self) -> Option<Self::Item> { | 87 | 9.06M | let attr = self.attrs.next().filter(|a| !a.is_empty())?; | 88 | 7.03M | self.parse_attr(attr).into() | 89 | 9.06M | } |
<gix_attributes::parse::Iter as core::iter::traits::iterator::Iterator>::next Line | Count | Source | 86 | 6.63M | fn next(&mut self) -> Option<Self::Item> { | 87 | 6.63M | let attr = self.attrs.next().filter(|a| !a.is_empty())?; | 88 | 6.63M | self.parse_attr(attr).into() | 89 | 6.63M | } |
|
90 | | } |
91 | | |
92 | | /// Instantiation |
93 | | impl<'a> Lines<'a> { |
94 | | /// Create a new instance to parse all attributes in all lines of the input `bytes`. |
95 | 7.32k | pub fn new(bytes: &'a [u8]) -> Self { |
96 | 7.32k | let bom = unicode_bom::Bom::from(bytes); |
97 | 7.32k | Lines { |
98 | 7.32k | lines: bytes[bom.len()..].lines(), |
99 | 7.32k | line_no: 0, |
100 | 7.32k | } |
101 | 7.32k | } Unexecuted instantiation: <gix_attributes::parse::Lines>::new <gix_attributes::parse::Lines>::new Line | Count | Source | 95 | 7.32k | pub fn new(bytes: &'a [u8]) -> Self { | 96 | 7.32k | let bom = unicode_bom::Bom::from(bytes); | 97 | 7.32k | Lines { | 98 | 7.32k | lines: bytes[bom.len()..].lines(), | 99 | 7.32k | line_no: 0, | 100 | 7.32k | } | 101 | 7.32k | } |
Unexecuted instantiation: <gix_attributes::parse::Lines>::new |
102 | | } |
103 | | |
104 | | impl<'a> Iterator for Lines<'a> { |
105 | | type Item = Result<(Kind, Iter<'a>, usize), Error>; |
106 | | |
107 | 2.09M | fn next(&mut self) -> Option<Self::Item> { |
108 | 4.07M | fn skip_blanks(line: &BStr) -> &BStr { |
109 | 4.07M | line.find_not_byteset(BLANKS).map_or(line, |pos| &line[pos..]) Unexecuted instantiation: <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next::skip_blanks::{closure#0}<gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next::skip_blanks::{closure#0}Line | Count | Source | 109 | 2.13M | line.find_not_byteset(BLANKS).map_or(line, |pos| &line[pos..]) |
Unexecuted instantiation: <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next::skip_blanks::{closure#0} |
110 | 4.07M | } Unexecuted instantiation: <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next::skip_blanks <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next::skip_blanks Line | Count | Source | 108 | 4.07M | fn skip_blanks(line: &BStr) -> &BStr { | 109 | 4.07M | line.find_not_byteset(BLANKS).map_or(line, |pos| &line[pos..]) | 110 | 4.07M | } |
Unexecuted instantiation: <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next::skip_blanks |
111 | 4.07M | for line in self.lines.by_ref() { |
112 | 4.07M | self.line_no += 1; |
113 | 4.07M | let line = skip_blanks(line.into()); |
114 | 4.07M | if line.first() == Some(&b'#') { |
115 | 51.6k | continue; |
116 | 4.02M | } |
117 | 4.02M | match parse_line(line, self.line_no) { |
118 | 1.93M | None => continue, |
119 | 2.08M | Some(res) => return Some(res), |
120 | | } |
121 | | } |
122 | 7.32k | None |
123 | 2.09M | } Unexecuted instantiation: <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next Line | Count | Source | 107 | 2.09M | fn next(&mut self) -> Option<Self::Item> { | 108 | | fn skip_blanks(line: &BStr) -> &BStr { | 109 | | line.find_not_byteset(BLANKS).map_or(line, |pos| &line[pos..]) | 110 | | } | 111 | 4.07M | for line in self.lines.by_ref() { | 112 | 4.07M | self.line_no += 1; | 113 | 4.07M | let line = skip_blanks(line.into()); | 114 | 4.07M | if line.first() == Some(&b'#') { | 115 | 51.6k | continue; | 116 | 4.02M | } | 117 | 4.02M | match parse_line(line, self.line_no) { | 118 | 1.93M | None => continue, | 119 | 2.08M | Some(res) => return Some(res), | 120 | | } | 121 | | } | 122 | 7.32k | None | 123 | 2.09M | } |
Unexecuted instantiation: <gix_attributes::parse::Lines as core::iter::traits::iterator::Iterator>::next |
124 | | } |
125 | | |
126 | 4.02M | fn parse_line(line: &BStr, line_number: usize) -> Option<Result<(Kind, Iter<'_>, usize), Error>> { |
127 | 4.02M | if line.is_empty() { |
128 | 1.93M | return None; |
129 | 2.08M | } |
130 | | |
131 | 2.08M | let (line, attrs): (Cow<'_, _>, _) = if line.starts_with(b"\"") { |
132 | 28.0k | let (unquoted, consumed) = match gix_quote::ansi_c::undo(line) { |
133 | 20.8k | Ok(res) => res, |
134 | 7.11k | Err(err) => return Some(Err(err.into())), |
135 | | }; |
136 | 20.8k | (unquoted, &line[consumed..]) |
137 | | } else { |
138 | 2.05M | line.find_byteset(BLANKS) |
139 | 2.05M | .map(|pos| (line[..pos].as_bstr().into(), line[pos..].as_bstr())) Unexecuted instantiation: gix_attributes::parse::parse_line::{closure#0}gix_attributes::parse::parse_line::{closure#0}Line | Count | Source | 139 | 783k | .map(|pos| (line[..pos].as_bstr().into(), line[pos..].as_bstr())) |
Unexecuted instantiation: gix_attributes::parse::parse_line::{closure#0} |
140 | 2.05M | .unwrap_or((line.into(), [].as_bstr())) |
141 | | }; |
142 | | |
143 | 2.08M | let kind_res = match line.strip_prefix(b"[attr]") { |
144 | 20.2k | Some(macro_name) => check_attr(macro_name.into()) |
145 | 20.2k | .map_err(|err| Error::MacroName { |
146 | 2.10k | line_number, |
147 | 2.10k | macro_name: err.attribute, |
148 | 2.10k | }) Unexecuted instantiation: gix_attributes::parse::parse_line::{closure#1}gix_attributes::parse::parse_line::{closure#1}Line | Count | Source | 146 | 2.10k | line_number, | 147 | 2.10k | macro_name: err.attribute, | 148 | 2.10k | }) |
Unexecuted instantiation: gix_attributes::parse::parse_line::{closure#1} |
149 | 20.2k | .map(|name| Kind::Macro(name.to_owned())), Unexecuted instantiation: gix_attributes::parse::parse_line::{closure#2}gix_attributes::parse::parse_line::{closure#2}Line | Count | Source | 149 | 18.1k | .map(|name| Kind::Macro(name.to_owned())), |
Unexecuted instantiation: gix_attributes::parse::parse_line::{closure#2} |
150 | | None => { |
151 | 2.06M | let pattern = gix_glob::Pattern::from_bytes(line.as_ref())?; |
152 | 2.05M | if pattern.mode.contains(gix_glob::pattern::Mode::NEGATIVE) { |
153 | 5.94k | Err(Error::PatternNegation { |
154 | 5.94k | line: line.into_owned(), |
155 | 5.94k | line_number, |
156 | 5.94k | }) |
157 | | } else { |
158 | 2.04M | Ok(Kind::Pattern(pattern)) |
159 | | } |
160 | | } |
161 | | }; |
162 | 2.07M | let kind = match kind_res { |
163 | 2.06M | Ok(kind) => kind, |
164 | 8.05k | Err(err) => return Some(Err(err)), |
165 | | }; |
166 | 2.06M | Ok((kind, Iter::new(attrs), line_number)).into() |
167 | 4.02M | } Unexecuted instantiation: gix_attributes::parse::parse_line gix_attributes::parse::parse_line Line | Count | Source | 126 | 4.02M | fn parse_line(line: &BStr, line_number: usize) -> Option<Result<(Kind, Iter<'_>, usize), Error>> { | 127 | 4.02M | if line.is_empty() { | 128 | 1.93M | return None; | 129 | 2.08M | } | 130 | | | 131 | 2.08M | let (line, attrs): (Cow<'_, _>, _) = if line.starts_with(b"\"") { | 132 | 28.0k | let (unquoted, consumed) = match gix_quote::ansi_c::undo(line) { | 133 | 20.8k | Ok(res) => res, | 134 | 7.11k | Err(err) => return Some(Err(err.into())), | 135 | | }; | 136 | 20.8k | (unquoted, &line[consumed..]) | 137 | | } else { | 138 | 2.05M | line.find_byteset(BLANKS) | 139 | 2.05M | .map(|pos| (line[..pos].as_bstr().into(), line[pos..].as_bstr())) | 140 | 2.05M | .unwrap_or((line.into(), [].as_bstr())) | 141 | | }; | 142 | | | 143 | 2.08M | let kind_res = match line.strip_prefix(b"[attr]") { | 144 | 20.2k | Some(macro_name) => check_attr(macro_name.into()) | 145 | 20.2k | .map_err(|err| Error::MacroName { | 146 | | line_number, | 147 | | macro_name: err.attribute, | 148 | | }) | 149 | 20.2k | .map(|name| Kind::Macro(name.to_owned())), | 150 | | None => { | 151 | 2.06M | let pattern = gix_glob::Pattern::from_bytes(line.as_ref())?; | 152 | 2.05M | if pattern.mode.contains(gix_glob::pattern::Mode::NEGATIVE) { | 153 | 5.94k | Err(Error::PatternNegation { | 154 | 5.94k | line: line.into_owned(), | 155 | 5.94k | line_number, | 156 | 5.94k | }) | 157 | | } else { | 158 | 2.04M | Ok(Kind::Pattern(pattern)) | 159 | | } | 160 | | } | 161 | | }; | 162 | 2.07M | let kind = match kind_res { | 163 | 2.06M | Ok(kind) => kind, | 164 | 8.05k | Err(err) => return Some(Err(err)), | 165 | | }; | 166 | 2.06M | Ok((kind, Iter::new(attrs), line_number)).into() | 167 | 4.02M | } |
Unexecuted instantiation: gix_attributes::parse::parse_line |
168 | | |
169 | | const BLANKS: &[u8] = b" \t\r"; |