##// END OF EJS Templates
rhg: Align "malformed --config" error message with Python...
Simon Sapin -
r47461:28a54c12 default
parent child Browse files
Show More
@@ -1,292 +1,292
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::{write_bytes, DisplayBytes};
12 use format_bytes::{write_bytes, DisplayBytes};
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::path::{Path, PathBuf};
16 use std::path::{Path, PathBuf};
17
17
18 lazy_static! {
18 lazy_static! {
19 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
19 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
20 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)?)");
21 /// Continuation whitespace
21 /// Continuation whitespace
22 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*$");
23 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
23 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
24 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
24 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
25 /// A directive that allows for removing previous entries
25 /// A directive that allows for removing previous entries
26 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
26 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
27 /// A directive that allows for including other config files
27 /// A directive that allows for including other config files
28 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*$");
29 }
29 }
30
30
31 /// All config values separated by layers of precedence.
31 /// All config values separated by layers of precedence.
32 /// 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
33 /// are used.
33 /// are used.
34 /// TODO detail the general precedence
34 /// TODO detail the general precedence
35 #[derive(Clone)]
35 #[derive(Clone)]
36 pub struct ConfigLayer {
36 pub struct ConfigLayer {
37 /// Mapping of the sections to their items
37 /// Mapping of the sections to their items
38 sections: HashMap<Vec<u8>, ConfigItem>,
38 sections: HashMap<Vec<u8>, ConfigItem>,
39 /// 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
40 pub origin: ConfigOrigin,
40 pub origin: ConfigOrigin,
41 /// Whether this layer comes from a trusted user or group
41 /// Whether this layer comes from a trusted user or group
42 pub trusted: bool,
42 pub trusted: bool,
43 }
43 }
44
44
45 impl ConfigLayer {
45 impl ConfigLayer {
46 pub fn new(origin: ConfigOrigin) -> Self {
46 pub fn new(origin: ConfigOrigin) -> Self {
47 ConfigLayer {
47 ConfigLayer {
48 sections: HashMap::new(),
48 sections: HashMap::new(),
49 trusted: true, // TODO check
49 trusted: true, // TODO check
50 origin,
50 origin,
51 }
51 }
52 }
52 }
53
53
54 /// Parse `--config` CLI arguments and return a layer if there’s any
54 /// Parse `--config` CLI arguments and return a layer if there’s any
55 pub(crate) fn parse_cli_args(
55 pub(crate) fn parse_cli_args(
56 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
56 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
57 ) -> Result<Option<Self>, ConfigError> {
57 ) -> Result<Option<Self>, ConfigError> {
58 fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
58 fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
59 use crate::utils::SliceExt;
59 use crate::utils::SliceExt;
60
60
61 let (section_and_item, value) = arg.split_2(b'=')?;
61 let (section_and_item, value) = arg.split_2(b'=')?;
62 let (section, item) = section_and_item.trim().split_2(b'.')?;
62 let (section, item) = section_and_item.trim().split_2(b'.')?;
63 Some((
63 Some((
64 section.to_owned(),
64 section.to_owned(),
65 item.to_owned(),
65 item.to_owned(),
66 value.trim().to_owned(),
66 value.trim().to_owned(),
67 ))
67 ))
68 }
68 }
69
69
70 let mut layer = Self::new(ConfigOrigin::CommandLine);
70 let mut layer = Self::new(ConfigOrigin::CommandLine);
71 for arg in cli_config_args {
71 for arg in cli_config_args {
72 let arg = arg.as_ref();
72 let arg = arg.as_ref();
73 if let Some((section, item, value)) = parse_one(arg) {
73 if let Some((section, item, value)) = parse_one(arg) {
74 layer.add(section, item, value, None);
74 layer.add(section, item, value, None);
75 } else {
75 } else {
76 Err(HgError::abort(format!(
76 Err(HgError::abort(format!(
77 "malformed --config option: \"{}\" \
77 "malformed --config option: '{}' \
78 (use --config section.name=value)",
78 (use --config section.name=value)",
79 String::from_utf8_lossy(arg),
79 String::from_utf8_lossy(arg),
80 )))?
80 )))?
81 }
81 }
82 }
82 }
83 if layer.sections.is_empty() {
83 if layer.sections.is_empty() {
84 Ok(None)
84 Ok(None)
85 } else {
85 } else {
86 Ok(Some(layer))
86 Ok(Some(layer))
87 }
87 }
88 }
88 }
89
89
90 /// Returns whether this layer comes from `--config` CLI arguments
90 /// Returns whether this layer comes from `--config` CLI arguments
91 pub(crate) fn is_from_command_line(&self) -> bool {
91 pub(crate) fn is_from_command_line(&self) -> bool {
92 if let ConfigOrigin::CommandLine = self.origin {
92 if let ConfigOrigin::CommandLine = self.origin {
93 true
93 true
94 } else {
94 } else {
95 false
95 false
96 }
96 }
97 }
97 }
98
98
99 /// Add an entry to the config, overwriting the old one if already present.
99 /// Add an entry to the config, overwriting the old one if already present.
100 pub fn add(
100 pub fn add(
101 &mut self,
101 &mut self,
102 section: Vec<u8>,
102 section: Vec<u8>,
103 item: Vec<u8>,
103 item: Vec<u8>,
104 value: Vec<u8>,
104 value: Vec<u8>,
105 line: Option<usize>,
105 line: Option<usize>,
106 ) {
106 ) {
107 self.sections
107 self.sections
108 .entry(section)
108 .entry(section)
109 .or_insert_with(|| HashMap::new())
109 .or_insert_with(|| HashMap::new())
110 .insert(item, ConfigValue { bytes: value, line });
110 .insert(item, ConfigValue { bytes: value, line });
111 }
111 }
112
112
113 /// Returns the config value in `<section>.<item>` if it exists
113 /// Returns the config value in `<section>.<item>` if it exists
114 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
114 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
115 Some(self.sections.get(section)?.get(item)?)
115 Some(self.sections.get(section)?.get(item)?)
116 }
116 }
117
117
118 pub fn is_empty(&self) -> bool {
118 pub fn is_empty(&self) -> bool {
119 self.sections.is_empty()
119 self.sections.is_empty()
120 }
120 }
121
121
122 /// Returns a `Vec` of layers in order of precedence (so, in read order),
122 /// Returns a `Vec` of layers in order of precedence (so, in read order),
123 /// recursively parsing the `%include` directives if any.
123 /// recursively parsing the `%include` directives if any.
124 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
124 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
125 let mut layers = vec![];
125 let mut layers = vec![];
126
126
127 // Discard byte order mark if any
127 // Discard byte order mark if any
128 let data = if data.starts_with(b"\xef\xbb\xbf") {
128 let data = if data.starts_with(b"\xef\xbb\xbf") {
129 &data[3..]
129 &data[3..]
130 } else {
130 } else {
131 data
131 data
132 };
132 };
133
133
134 // TODO check if it's trusted
134 // TODO check if it's trusted
135 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
135 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
136
136
137 let mut lines_iter =
137 let mut lines_iter =
138 data.split(|b| *b == b'\n').enumerate().peekable();
138 data.split(|b| *b == b'\n').enumerate().peekable();
139 let mut section = b"".to_vec();
139 let mut section = b"".to_vec();
140
140
141 while let Some((index, bytes)) = lines_iter.next() {
141 while let Some((index, bytes)) = lines_iter.next() {
142 if let Some(m) = INCLUDE_RE.captures(&bytes) {
142 if let Some(m) = INCLUDE_RE.captures(&bytes) {
143 let filename_bytes = &m[1];
143 let filename_bytes = &m[1];
144 // `Path::parent` only fails for the root directory,
144 // `Path::parent` only fails for the root directory,
145 // which `src` can’t be since we’ve managed to open it as a
145 // which `src` can’t be since we’ve managed to open it as a
146 // file.
146 // file.
147 let dir = src
147 let dir = src
148 .parent()
148 .parent()
149 .expect("Path::parent fail on a file we’ve read");
149 .expect("Path::parent fail on a file we’ve read");
150 // `Path::join` with an absolute argument correctly ignores the
150 // `Path::join` with an absolute argument correctly ignores the
151 // base path
151 // base path
152 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
152 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
153 let data =
153 let data =
154 std::fs::read(&filename).when_reading_file(&filename)?;
154 std::fs::read(&filename).when_reading_file(&filename)?;
155 layers.push(current_layer);
155 layers.push(current_layer);
156 layers.extend(Self::parse(&filename, &data)?);
156 layers.extend(Self::parse(&filename, &data)?);
157 current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
157 current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
158 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
158 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
159 } else if let Some(m) = SECTION_RE.captures(&bytes) {
159 } else if let Some(m) = SECTION_RE.captures(&bytes) {
160 section = m[1].to_vec();
160 section = m[1].to_vec();
161 } else if let Some(m) = ITEM_RE.captures(&bytes) {
161 } else if let Some(m) = ITEM_RE.captures(&bytes) {
162 let item = m[1].to_vec();
162 let item = m[1].to_vec();
163 let mut value = m[2].to_vec();
163 let mut value = m[2].to_vec();
164 loop {
164 loop {
165 match lines_iter.peek() {
165 match lines_iter.peek() {
166 None => break,
166 None => break,
167 Some((_, v)) => {
167 Some((_, v)) => {
168 if let Some(_) = COMMENT_RE.captures(&v) {
168 if let Some(_) = COMMENT_RE.captures(&v) {
169 } else if let Some(_) = CONT_RE.captures(&v) {
169 } else if let Some(_) = CONT_RE.captures(&v) {
170 value.extend(b"\n");
170 value.extend(b"\n");
171 value.extend(&m[1]);
171 value.extend(&m[1]);
172 } else {
172 } else {
173 break;
173 break;
174 }
174 }
175 }
175 }
176 };
176 };
177 lines_iter.next();
177 lines_iter.next();
178 }
178 }
179 current_layer.add(
179 current_layer.add(
180 section.clone(),
180 section.clone(),
181 item,
181 item,
182 value,
182 value,
183 Some(index + 1),
183 Some(index + 1),
184 );
184 );
185 } else if let Some(m) = UNSET_RE.captures(&bytes) {
185 } else if let Some(m) = UNSET_RE.captures(&bytes) {
186 if let Some(map) = current_layer.sections.get_mut(&section) {
186 if let Some(map) = current_layer.sections.get_mut(&section) {
187 map.remove(&m[1]);
187 map.remove(&m[1]);
188 }
188 }
189 } else {
189 } else {
190 return Err(ConfigParseError {
190 return Err(ConfigParseError {
191 origin: ConfigOrigin::File(src.to_owned()),
191 origin: ConfigOrigin::File(src.to_owned()),
192 line: Some(index + 1),
192 line: Some(index + 1),
193 bytes: bytes.to_owned(),
193 bytes: bytes.to_owned(),
194 }
194 }
195 .into());
195 .into());
196 }
196 }
197 }
197 }
198 if !current_layer.is_empty() {
198 if !current_layer.is_empty() {
199 layers.push(current_layer);
199 layers.push(current_layer);
200 }
200 }
201 Ok(layers)
201 Ok(layers)
202 }
202 }
203 }
203 }
204
204
205 impl DisplayBytes for ConfigLayer {
205 impl DisplayBytes for ConfigLayer {
206 fn display_bytes(
206 fn display_bytes(
207 &self,
207 &self,
208 out: &mut dyn std::io::Write,
208 out: &mut dyn std::io::Write,
209 ) -> std::io::Result<()> {
209 ) -> std::io::Result<()> {
210 let mut sections: Vec<_> = self.sections.iter().collect();
210 let mut sections: Vec<_> = self.sections.iter().collect();
211 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
211 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
212
212
213 for (section, items) in sections.into_iter() {
213 for (section, items) in sections.into_iter() {
214 let mut items: Vec<_> = items.into_iter().collect();
214 let mut items: Vec<_> = items.into_iter().collect();
215 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
215 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
216
216
217 for (item, config_entry) in items {
217 for (item, config_entry) in items {
218 write_bytes!(
218 write_bytes!(
219 out,
219 out,
220 b"{}.{}={} # {}\n",
220 b"{}.{}={} # {}\n",
221 section,
221 section,
222 item,
222 item,
223 &config_entry.bytes,
223 &config_entry.bytes,
224 &self.origin,
224 &self.origin,
225 )?
225 )?
226 }
226 }
227 }
227 }
228 Ok(())
228 Ok(())
229 }
229 }
230 }
230 }
231
231
232 /// Mapping of section item to value.
232 /// Mapping of section item to value.
233 /// In the following:
233 /// In the following:
234 /// ```text
234 /// ```text
235 /// [ui]
235 /// [ui]
236 /// paginate=no
236 /// paginate=no
237 /// ```
237 /// ```
238 /// "paginate" is the section item and "no" the value.
238 /// "paginate" is the section item and "no" the value.
239 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
239 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
240
240
241 #[derive(Clone, Debug, PartialEq)]
241 #[derive(Clone, Debug, PartialEq)]
242 pub struct ConfigValue {
242 pub struct ConfigValue {
243 /// The raw bytes of the value (be it from the CLI, env or from a file)
243 /// The raw bytes of the value (be it from the CLI, env or from a file)
244 pub bytes: Vec<u8>,
244 pub bytes: Vec<u8>,
245 /// Only present if the value comes from a file, 1-indexed.
245 /// Only present if the value comes from a file, 1-indexed.
246 pub line: Option<usize>,
246 pub line: Option<usize>,
247 }
247 }
248
248
249 #[derive(Clone, Debug)]
249 #[derive(Clone, Debug)]
250 pub enum ConfigOrigin {
250 pub enum ConfigOrigin {
251 /// From a configuration file
251 /// From a configuration file
252 File(PathBuf),
252 File(PathBuf),
253 /// From a `--config` CLI argument
253 /// From a `--config` CLI argument
254 CommandLine,
254 CommandLine,
255 /// From environment variables like `$PAGER` or `$EDITOR`
255 /// From environment variables like `$PAGER` or `$EDITOR`
256 Environment(Vec<u8>),
256 Environment(Vec<u8>),
257 /* TODO cli
257 /* TODO cli
258 * TODO defaults (configitems.py)
258 * TODO defaults (configitems.py)
259 * TODO extensions
259 * TODO extensions
260 * TODO Python resources?
260 * TODO Python resources?
261 * Others? */
261 * Others? */
262 }
262 }
263
263
264 impl DisplayBytes for ConfigOrigin {
264 impl DisplayBytes for ConfigOrigin {
265 fn display_bytes(
265 fn display_bytes(
266 &self,
266 &self,
267 out: &mut dyn std::io::Write,
267 out: &mut dyn std::io::Write,
268 ) -> std::io::Result<()> {
268 ) -> std::io::Result<()> {
269 match self {
269 match self {
270 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
270 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
271 ConfigOrigin::CommandLine => out.write_all(b"--config"),
271 ConfigOrigin::CommandLine => out.write_all(b"--config"),
272 ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
272 ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
273 }
273 }
274 }
274 }
275 }
275 }
276
276
277 #[derive(Debug)]
277 #[derive(Debug)]
278 pub struct ConfigParseError {
278 pub struct ConfigParseError {
279 pub origin: ConfigOrigin,
279 pub origin: ConfigOrigin,
280 pub line: Option<usize>,
280 pub line: Option<usize>,
281 pub bytes: Vec<u8>,
281 pub bytes: Vec<u8>,
282 }
282 }
283
283
284 #[derive(Debug, derive_more::From)]
284 #[derive(Debug, derive_more::From)]
285 pub enum ConfigError {
285 pub enum ConfigError {
286 Parse(ConfigParseError),
286 Parse(ConfigParseError),
287 Other(HgError),
287 Other(HgError),
288 }
288 }
289
289
290 fn make_regex(pattern: &'static str) -> Regex {
290 fn make_regex(pattern: &'static str) -> Regex {
291 Regex::new(pattern).expect("expected a valid regex")
291 Regex::new(pattern).expect("expected a valid regex")
292 }
292 }
General Comments 0
You need to be logged in to leave comments. Login now