gix_config/parse/section/
mod.rs1use crate::parse::{MaybeDecoded, Span};
2
3pub mod header;
5
6pub(crate) mod unvalidated;
7
8#[derive(Clone, Debug)]
10pub(crate) struct HeaderData {
11 pub(crate) name: Span,
13 pub(crate) separator: Option<Span>,
19 pub(crate) subsection_name: Option<MaybeDecoded>,
20}
21
22mod types {
23 use bstr::ByteSlice;
24
25 macro_rules! generate_case_insensitive {
26 ($name:ident, $module:ident, $err_doc:literal, $validate:ident, $cow_inner_type:ty, $comment:literal) => {
27 pub mod $module {
29 #[derive(Debug, thiserror::Error, Copy, Clone)]
31 #[error($err_doc)]
32 pub struct Error;
33 }
34
35 #[doc = $comment]
36 #[derive(Clone, Eq, Debug, Default)]
37 pub struct $name(pub(crate) bstr::BString);
38
39 impl $name {
40 pub(crate) fn from_str_unchecked(s: &str) -> Self {
41 $name(s.into())
42 }
43 #[must_use]
45 pub fn to_owned(&self) -> $name {
46 self.clone()
47 }
48 }
49
50 impl PartialEq for $name {
51 fn eq(&self, other: &Self) -> bool {
52 self.0.eq_ignore_ascii_case(&other.0)
53 }
54 }
55
56 impl std::fmt::Display for $name {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 std::fmt::Display::fmt(&self.0, f)
59 }
60 }
61
62 impl PartialOrd for $name {
63 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
64 Some(self.cmp(other))
65 }
66 }
67
68 impl Ord for $name {
69 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
70 let a = self.0.iter().map(|c| c.to_ascii_lowercase());
71 let b = other.0.iter().map(|c| c.to_ascii_lowercase());
72 a.cmp(b)
73 }
74 }
75
76 impl std::hash::Hash for $name {
77 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
78 for b in self.0.iter() {
79 b.to_ascii_lowercase().hash(state);
80 }
81 }
82 }
83
84 impl std::convert::TryFrom<&str> for $name {
85 type Error = $module::Error;
86
87 fn try_from(s: &str) -> Result<Self, Self::Error> {
88 Self::try_from(bstr::ByteSlice::as_bstr(s.as_bytes()))
89 }
90 }
91
92 impl std::convert::TryFrom<String> for $name {
93 type Error = $module::Error;
94
95 fn try_from(s: String) -> Result<Self, Self::Error> {
96 Self::try_from(bstr::BString::from(s))
97 }
98 }
99
100 impl std::convert::TryFrom<bstr::BString> for $name {
101 type Error = $module::Error;
102
103 fn try_from(s: bstr::BString) -> Result<Self, Self::Error> {
104 if $validate(s.as_slice().as_bstr()) {
105 Ok(Self(s.into()))
106 } else {
107 Err($module::Error)
108 }
109 }
110 }
111
112 impl std::convert::TryFrom<&bstr::BStr> for $name {
113 type Error = $module::Error;
114
115 fn try_from(s: &bstr::BStr) -> Result<Self, Self::Error> {
116 if $validate(s) {
117 Ok(Self(s.into()))
118 } else {
119 Err($module::Error)
120 }
121 }
122 }
123
124 impl std::ops::Deref for $name {
125 type Target = $cow_inner_type;
126
127 fn deref(&self) -> &Self::Target {
128 self.0.as_bstr()
129 }
130 }
131
132 impl std::convert::AsRef<str> for $name {
133 fn as_ref(&self) -> &str {
134 std::str::from_utf8(self.0.as_slice()).expect("only valid UTF8 makes it through our validation")
135 }
136 }
137 };
138 }
139
140 fn is_valid_name(n: &bstr::BStr) -> bool {
141 !n.is_empty() && n.iter().all(|b| b.is_ascii_alphanumeric() || *b == b'-')
142 }
143 fn is_valid_value_name(n: &bstr::BStr) -> bool {
144 is_valid_name(n) && n[0].is_ascii_alphabetic()
145 }
146
147 generate_case_insensitive!(
148 Name,
149 name,
150 "Valid names consist of alphanumeric characters or dashes.",
151 is_valid_name,
152 bstr::BStr,
153 "Wrapper struct for section header names, like `remote`, since these are case-insensitive."
154 );
155
156 generate_case_insensitive!(
157 ValueName,
158 value_name,
159 "Valid value names consist of alphanumeric characters or dashes, starting with an alphabetic character.",
160 is_valid_value_name,
161 bstr::BStr,
162 "Wrapper struct for value names, like `path` in `include.path`, since keys are case-insensitive."
163 );
164}
165pub(crate) use types::ValueName;
166pub use types::{Name, name, value_name};
167
168#[cfg(test)]
169mod tests {
170 use std::cmp::Ordering;
171
172 use super::ValueName;
173
174 fn key(key: &str) -> ValueName {
175 ValueName::try_from(key).expect("valid test key")
176 }
177
178 #[test]
179 fn value_names_reject_invalid_formats() {
180 for invalid in ["", "1a", "a.2", "##", "\""] {
181 assert!(ValueName::try_from(invalid).is_err(), "{invalid:?} is invalid");
182 }
183 }
184
185 #[test]
186 fn value_names_are_case_insensitive() {
187 assert_eq!(key("aB-c"), key("Ab-C"));
188 assert_eq!(key("a").cmp(&key("a")), Ordering::Equal);
189 assert_eq!(key("aBc").cmp(&key("AbC")), Ordering::Equal);
190
191 fn calculate_hash<T: std::hash::Hash>(value: T) -> u64 {
192 use std::hash::Hasher;
193 let mut state = std::collections::hash_map::DefaultHasher::new();
194 value.hash(&mut state);
195 state.finish()
196 }
197 assert_eq!(calculate_hash(key("aBc")), calculate_hash(key("AbC")));
198 }
199}