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