##// END OF EJS Templates
rust: Remove unnecessary check for absolute path before joining...
Simon Sapin -
r47211:39128182 default
parent child Browse files
Show More
@@ -1,251 +1,239
1 1 // layer.rs
2 2 //
3 3 // Copyright 2020
4 4 // Valentin Gatien-Baron,
5 5 // Raphaël Gomès <rgomes@octobus.net>
6 6 //
7 7 // This software may be used and distributed according to the terms of the
8 8 // GNU General Public License version 2 or any later version.
9 9
10 10 use crate::errors::{HgError, IoResultExt};
11 11 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
12 12 use format_bytes::format_bytes;
13 13 use lazy_static::lazy_static;
14 14 use regex::bytes::Regex;
15 15 use std::collections::HashMap;
16 use std::io;
17 16 use std::path::{Path, PathBuf};
18 17
19 18 lazy_static! {
20 19 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
21 20 static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
22 21 /// Continuation whitespace
23 22 static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
24 23 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
25 24 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
26 25 /// A directive that allows for removing previous entries
27 26 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
28 27 /// A directive that allows for including other config files
29 28 static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
30 29 }
31 30
32 31 /// All config values separated by layers of precedence.
33 32 /// Each config source may be split in multiple layers if `%include` directives
34 33 /// are used.
35 34 /// TODO detail the general precedence
36 35 #[derive(Clone)]
37 36 pub struct ConfigLayer {
38 37 /// Mapping of the sections to their items
39 38 sections: HashMap<Vec<u8>, ConfigItem>,
40 39 /// All sections (and their items/values) in a layer share the same origin
41 40 pub origin: ConfigOrigin,
42 41 /// Whether this layer comes from a trusted user or group
43 42 pub trusted: bool,
44 43 }
45 44
46 45 impl ConfigLayer {
47 46 pub fn new(origin: ConfigOrigin) -> Self {
48 47 ConfigLayer {
49 48 sections: HashMap::new(),
50 49 trusted: true, // TODO check
51 50 origin,
52 51 }
53 52 }
54 53
55 54 /// Add an entry to the config, overwriting the old one if already present.
56 55 pub fn add(
57 56 &mut self,
58 57 section: Vec<u8>,
59 58 item: Vec<u8>,
60 59 value: Vec<u8>,
61 60 line: Option<usize>,
62 61 ) {
63 62 self.sections
64 63 .entry(section)
65 64 .or_insert_with(|| HashMap::new())
66 65 .insert(item, ConfigValue { bytes: value, line });
67 66 }
68 67
69 68 /// Returns the config value in `<section>.<item>` if it exists
70 69 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
71 70 Some(self.sections.get(section)?.get(item)?)
72 71 }
73 72
74 73 pub fn is_empty(&self) -> bool {
75 74 self.sections.is_empty()
76 75 }
77 76
78 77 /// Returns a `Vec` of layers in order of precedence (so, in read order),
79 78 /// recursively parsing the `%include` directives if any.
80 79 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
81 80 let mut layers = vec![];
82 81
83 82 // Discard byte order mark if any
84 83 let data = if data.starts_with(b"\xef\xbb\xbf") {
85 84 &data[3..]
86 85 } else {
87 86 data
88 87 };
89 88
90 89 // TODO check if it's trusted
91 90 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
92 91
93 92 let mut lines_iter =
94 93 data.split(|b| *b == b'\n').enumerate().peekable();
95 94 let mut section = b"".to_vec();
96 95
97 96 while let Some((index, bytes)) = lines_iter.next() {
98 97 if let Some(m) = INCLUDE_RE.captures(&bytes) {
99 98 let filename_bytes = &m[1];
100 let filename_to_include = get_path_from_bytes(&filename_bytes);
101 let (include_src, result) =
102 read_include(&src, &filename_to_include);
103 let data = result.for_file(filename_to_include)?;
99 // `Path::parent` only fails for the root directory,
100 // which `src` can’t be since we’ve managed to open it as a file.
101 let dir = src
102 .parent()
103 .expect("Path::parent fail on a file we’ve read");
104 // `Path::join` with an absolute argument correctly ignores the base path
105 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
106 let data = std::fs::read(&filename).for_file(&filename)?;
104 107 layers.push(current_layer);
105 layers.extend(Self::parse(&include_src, &data)?);
108 layers.extend(Self::parse(&filename, &data)?);
106 109 current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
107 110 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
108 111 } else if let Some(m) = SECTION_RE.captures(&bytes) {
109 112 section = m[1].to_vec();
110 113 } else if let Some(m) = ITEM_RE.captures(&bytes) {
111 114 let item = m[1].to_vec();
112 115 let mut value = m[2].to_vec();
113 116 loop {
114 117 match lines_iter.peek() {
115 118 None => break,
116 119 Some((_, v)) => {
117 120 if let Some(_) = COMMENT_RE.captures(&v) {
118 121 } else if let Some(_) = CONT_RE.captures(&v) {
119 122 value.extend(b"\n");
120 123 value.extend(&m[1]);
121 124 } else {
122 125 break;
123 126 }
124 127 }
125 128 };
126 129 lines_iter.next();
127 130 }
128 131 current_layer.add(
129 132 section.clone(),
130 133 item,
131 134 value,
132 135 Some(index + 1),
133 136 );
134 137 } else if let Some(m) = UNSET_RE.captures(&bytes) {
135 138 if let Some(map) = current_layer.sections.get_mut(&section) {
136 139 map.remove(&m[1]);
137 140 }
138 141 } else {
139 142 return Err(ConfigParseError {
140 143 origin: ConfigOrigin::File(src.to_owned()),
141 144 line: Some(index + 1),
142 145 bytes: bytes.to_owned(),
143 146 }
144 147 .into());
145 148 }
146 149 }
147 150 if !current_layer.is_empty() {
148 151 layers.push(current_layer);
149 152 }
150 153 Ok(layers)
151 154 }
152 155 }
153 156
154 157 impl std::fmt::Debug for ConfigLayer {
155 158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 159 let mut sections: Vec<_> = self.sections.iter().collect();
157 160 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
158 161
159 162 for (section, items) in sections.into_iter() {
160 163 let mut items: Vec<_> = items.into_iter().collect();
161 164 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
162 165
163 166 for (item, config_entry) in items {
164 167 writeln!(
165 168 f,
166 169 "{}",
167 170 String::from_utf8_lossy(&format_bytes!(
168 171 b"{}.{}={} # {}",
169 172 section,
170 173 item,
171 174 &config_entry.bytes,
172 175 &self.origin.to_bytes(),
173 176 ))
174 177 )?
175 178 }
176 179 }
177 180 Ok(())
178 181 }
179 182 }
180 183
181 184 /// Mapping of section item to value.
182 185 /// In the following:
183 186 /// ```text
184 187 /// [ui]
185 188 /// paginate=no
186 189 /// ```
187 190 /// "paginate" is the section item and "no" the value.
188 191 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
189 192
190 193 #[derive(Clone, Debug, PartialEq)]
191 194 pub struct ConfigValue {
192 195 /// The raw bytes of the value (be it from the CLI, env or from a file)
193 196 pub bytes: Vec<u8>,
194 197 /// Only present if the value comes from a file, 1-indexed.
195 198 pub line: Option<usize>,
196 199 }
197 200
198 201 #[derive(Clone, Debug)]
199 202 pub enum ConfigOrigin {
200 203 /// The value comes from a configuration file
201 204 File(PathBuf),
202 205 /// The value comes from the environment like `$PAGER` or `$EDITOR`
203 206 Environment(Vec<u8>),
204 207 /* TODO cli
205 208 * TODO defaults (configitems.py)
206 209 * TODO extensions
207 210 * TODO Python resources?
208 211 * Others? */
209 212 }
210 213
211 214 impl ConfigOrigin {
212 215 /// TODO use some kind of dedicated trait?
213 216 pub fn to_bytes(&self) -> Vec<u8> {
214 217 match self {
215 218 ConfigOrigin::File(p) => get_bytes_from_path(p),
216 219 ConfigOrigin::Environment(e) => e.to_owned(),
217 220 }
218 221 }
219 222 }
220 223
221 224 #[derive(Debug)]
222 225 pub struct ConfigParseError {
223 226 pub origin: ConfigOrigin,
224 227 pub line: Option<usize>,
225 228 pub bytes: Vec<u8>,
226 229 }
227 230
228 231 #[derive(Debug, derive_more::From)]
229 232 pub enum ConfigError {
230 233 Parse(ConfigParseError),
231 234 Other(HgError),
232 235 }
233 236
234 237 fn make_regex(pattern: &'static str) -> Regex {
235 238 Regex::new(pattern).expect("expected a valid regex")
236 239 }
237
238 /// Includes are relative to the file they're defined in, unless they're
239 /// absolute.
240 fn read_include(
241 old_src: &Path,
242 new_src: &Path,
243 ) -> (PathBuf, io::Result<Vec<u8>>) {
244 if new_src.is_absolute() {
245 (new_src.to_path_buf(), std::fs::read(&new_src))
246 } else {
247 let dir = old_src.parent().unwrap();
248 let new_src = dir.join(&new_src);
249 (new_src.to_owned(), std::fs::read(&new_src))
250 }
251 }
General Comments 0
You need to be logged in to leave comments. Login now