##// END OF EJS Templates
rust: use HgError in ConfigError...
Simon Sapin -
r47176:0cb1b022 default
parent child Browse files
Show More
@@ -1,196 +1,198
1 // config.rs
1 // config.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 super::layer;
10 use super::layer;
11 use crate::config::layer::{ConfigError, ConfigLayer, ConfigValue};
11 use crate::config::layer::{
12 ConfigError, ConfigLayer, ConfigParseError, ConfigValue,
13 };
12 use std::path::PathBuf;
14 use std::path::PathBuf;
13
15
14 use crate::repo::Repo;
16 use crate::repo::Repo;
15 use crate::utils::files::read_whole_file;
17 use crate::utils::files::read_whole_file;
16
18
17 /// Holds the config values for the current repository
19 /// Holds the config values for the current repository
18 /// TODO update this docstring once we support more sources
20 /// TODO update this docstring once we support more sources
19 pub struct Config {
21 pub struct Config {
20 layers: Vec<layer::ConfigLayer>,
22 layers: Vec<layer::ConfigLayer>,
21 }
23 }
22
24
23 impl std::fmt::Debug for Config {
25 impl std::fmt::Debug for Config {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 for (index, layer) in self.layers.iter().rev().enumerate() {
27 for (index, layer) in self.layers.iter().rev().enumerate() {
26 write!(
28 write!(
27 f,
29 f,
28 "==== Layer {} (trusted: {}) ====\n{:?}",
30 "==== Layer {} (trusted: {}) ====\n{:?}",
29 index, layer.trusted, layer
31 index, layer.trusted, layer
30 )?;
32 )?;
31 }
33 }
32 Ok(())
34 Ok(())
33 }
35 }
34 }
36 }
35
37
36 pub enum ConfigSource {
38 pub enum ConfigSource {
37 /// Absolute path to a config file
39 /// Absolute path to a config file
38 AbsPath(PathBuf),
40 AbsPath(PathBuf),
39 /// Already parsed (from the CLI, env, Python resources, etc.)
41 /// Already parsed (from the CLI, env, Python resources, etc.)
40 Parsed(layer::ConfigLayer),
42 Parsed(layer::ConfigLayer),
41 }
43 }
42
44
43 pub fn parse_bool(v: &[u8]) -> Option<bool> {
45 pub fn parse_bool(v: &[u8]) -> Option<bool> {
44 match v.to_ascii_lowercase().as_slice() {
46 match v.to_ascii_lowercase().as_slice() {
45 b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true),
47 b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true),
46 b"0" | b"no" | b"false" | b"off" | b"never" => Some(false),
48 b"0" | b"no" | b"false" | b"off" | b"never" => Some(false),
47 _ => None,
49 _ => None,
48 }
50 }
49 }
51 }
50
52
51 impl Config {
53 impl Config {
52 /// Loads in order, which means that the precedence is the same
54 /// Loads in order, which means that the precedence is the same
53 /// as the order of `sources`.
55 /// as the order of `sources`.
54 pub fn load_from_explicit_sources(
56 pub fn load_from_explicit_sources(
55 sources: Vec<ConfigSource>,
57 sources: Vec<ConfigSource>,
56 ) -> Result<Self, ConfigError> {
58 ) -> Result<Self, ConfigError> {
57 let mut layers = vec![];
59 let mut layers = vec![];
58
60
59 for source in sources.into_iter() {
61 for source in sources.into_iter() {
60 match source {
62 match source {
61 ConfigSource::Parsed(c) => layers.push(c),
63 ConfigSource::Parsed(c) => layers.push(c),
62 ConfigSource::AbsPath(c) => {
64 ConfigSource::AbsPath(c) => {
63 // TODO check if it should be trusted
65 // TODO check if it should be trusted
64 // mercurial/ui.py:427
66 // mercurial/ui.py:427
65 let data = match read_whole_file(&c) {
67 let data = match read_whole_file(&c) {
66 Err(_) => continue, // same as the python code
68 Err(_) => continue, // same as the python code
67 Ok(data) => data,
69 Ok(data) => data,
68 };
70 };
69 layers.extend(ConfigLayer::parse(&c, &data)?)
71 layers.extend(ConfigLayer::parse(&c, &data)?)
70 }
72 }
71 }
73 }
72 }
74 }
73
75
74 Ok(Config { layers })
76 Ok(Config { layers })
75 }
77 }
76
78
77 /// Loads the local config. In a future version, this will also load the
79 /// Loads the local config. In a future version, this will also load the
78 /// `$HOME/.hgrc` and more to mirror the Python implementation.
80 /// `$HOME/.hgrc` and more to mirror the Python implementation.
79 pub fn load_for_repo(repo: &Repo) -> Result<Self, ConfigError> {
81 pub fn load_for_repo(repo: &Repo) -> Result<Self, ConfigError> {
80 Ok(Self::load_from_explicit_sources(vec![
82 Ok(Self::load_from_explicit_sources(vec![
81 ConfigSource::AbsPath(repo.hg_vfs().join("hgrc")),
83 ConfigSource::AbsPath(repo.hg_vfs().join("hgrc")),
82 ])?)
84 ])?)
83 }
85 }
84
86
85 /// Returns an `Err` if the first value found is not a valid boolean.
87 /// Returns an `Err` if the first value found is not a valid boolean.
86 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
88 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
87 /// found, or `None`.
89 /// found, or `None`.
88 pub fn get_option(
90 pub fn get_option(
89 &self,
91 &self,
90 section: &[u8],
92 section: &[u8],
91 item: &[u8],
93 item: &[u8],
92 ) -> Result<Option<bool>, ConfigError> {
94 ) -> Result<Option<bool>, ConfigParseError> {
93 match self.get_inner(&section, &item) {
95 match self.get_inner(&section, &item) {
94 Some((layer, v)) => match parse_bool(&v.bytes) {
96 Some((layer, v)) => match parse_bool(&v.bytes) {
95 Some(b) => Ok(Some(b)),
97 Some(b) => Ok(Some(b)),
96 None => Err(ConfigError::Parse {
98 None => Err(ConfigParseError {
97 origin: layer.origin.to_owned(),
99 origin: layer.origin.to_owned(),
98 line: v.line,
100 line: v.line,
99 bytes: v.bytes.to_owned(),
101 bytes: v.bytes.to_owned(),
100 }),
102 }),
101 },
103 },
102 None => Ok(None),
104 None => Ok(None),
103 }
105 }
104 }
106 }
105
107
106 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
108 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
107 /// if the value is not found, an `Err` if it's not a valid boolean.
109 /// if the value is not found, an `Err` if it's not a valid boolean.
108 pub fn get_bool(
110 pub fn get_bool(
109 &self,
111 &self,
110 section: &[u8],
112 section: &[u8],
111 item: &[u8],
113 item: &[u8],
112 ) -> Result<bool, ConfigError> {
114 ) -> Result<bool, ConfigError> {
113 Ok(self.get_option(section, item)?.unwrap_or(false))
115 Ok(self.get_option(section, item)?.unwrap_or(false))
114 }
116 }
115
117
116 /// Returns the raw value bytes of the first one found, or `None`.
118 /// Returns the raw value bytes of the first one found, or `None`.
117 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
119 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
118 self.get_inner(section, item)
120 self.get_inner(section, item)
119 .map(|(_, value)| value.bytes.as_ref())
121 .map(|(_, value)| value.bytes.as_ref())
120 }
122 }
121
123
122 /// Returns the layer and the value of the first one found, or `None`.
124 /// Returns the layer and the value of the first one found, or `None`.
123 fn get_inner(
125 fn get_inner(
124 &self,
126 &self,
125 section: &[u8],
127 section: &[u8],
126 item: &[u8],
128 item: &[u8],
127 ) -> Option<(&ConfigLayer, &ConfigValue)> {
129 ) -> Option<(&ConfigLayer, &ConfigValue)> {
128 for layer in self.layers.iter().rev() {
130 for layer in self.layers.iter().rev() {
129 if !layer.trusted {
131 if !layer.trusted {
130 continue;
132 continue;
131 }
133 }
132 if let Some(v) = layer.get(&section, &item) {
134 if let Some(v) = layer.get(&section, &item) {
133 return Some((&layer, v));
135 return Some((&layer, v));
134 }
136 }
135 }
137 }
136 None
138 None
137 }
139 }
138
140
139 /// Get raw values bytes from all layers (even untrusted ones) in order
141 /// Get raw values bytes from all layers (even untrusted ones) in order
140 /// of precedence.
142 /// of precedence.
141 #[cfg(test)]
143 #[cfg(test)]
142 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
144 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
143 let mut res = vec![];
145 let mut res = vec![];
144 for layer in self.layers.iter().rev() {
146 for layer in self.layers.iter().rev() {
145 if let Some(v) = layer.get(&section, &item) {
147 if let Some(v) = layer.get(&section, &item) {
146 res.push(v.bytes.as_ref());
148 res.push(v.bytes.as_ref());
147 }
149 }
148 }
150 }
149 res
151 res
150 }
152 }
151 }
153 }
152
154
153 #[cfg(test)]
155 #[cfg(test)]
154 mod tests {
156 mod tests {
155 use super::*;
157 use super::*;
156 use pretty_assertions::assert_eq;
158 use pretty_assertions::assert_eq;
157 use std::fs::File;
159 use std::fs::File;
158 use std::io::Write;
160 use std::io::Write;
159
161
160 #[test]
162 #[test]
161 fn test_include_layer_ordering() {
163 fn test_include_layer_ordering() {
162 let tmpdir = tempfile::tempdir().unwrap();
164 let tmpdir = tempfile::tempdir().unwrap();
163 let tmpdir_path = tmpdir.path();
165 let tmpdir_path = tmpdir.path();
164 let mut included_file =
166 let mut included_file =
165 File::create(&tmpdir_path.join("included.rc")).unwrap();
167 File::create(&tmpdir_path.join("included.rc")).unwrap();
166
168
167 included_file.write_all(b"[section]\nitem=value1").unwrap();
169 included_file.write_all(b"[section]\nitem=value1").unwrap();
168 let base_config_path = tmpdir_path.join("base.rc");
170 let base_config_path = tmpdir_path.join("base.rc");
169 let mut config_file = File::create(&base_config_path).unwrap();
171 let mut config_file = File::create(&base_config_path).unwrap();
170 let data =
172 let data =
171 b"[section]\nitem=value0\n%include included.rc\nitem=value2";
173 b"[section]\nitem=value0\n%include included.rc\nitem=value2";
172 config_file.write_all(data).unwrap();
174 config_file.write_all(data).unwrap();
173
175
174 let sources = vec![ConfigSource::AbsPath(base_config_path)];
176 let sources = vec![ConfigSource::AbsPath(base_config_path)];
175 let config = Config::load_from_explicit_sources(sources)
177 let config = Config::load_from_explicit_sources(sources)
176 .expect("expected valid config");
178 .expect("expected valid config");
177
179
178 dbg!(&config);
180 dbg!(&config);
179
181
180 let (_, value) = config.get_inner(b"section", b"item").unwrap();
182 let (_, value) = config.get_inner(b"section", b"item").unwrap();
181 assert_eq!(
183 assert_eq!(
182 value,
184 value,
183 &ConfigValue {
185 &ConfigValue {
184 bytes: b"value2".to_vec(),
186 bytes: b"value2".to_vec(),
185 line: Some(4)
187 line: Some(4)
186 }
188 }
187 );
189 );
188
190
189 let value = config.get(b"section", b"item").unwrap();
191 let value = config.get(b"section", b"item").unwrap();
190 assert_eq!(value, b"value2",);
192 assert_eq!(value, b"value2",);
191 assert_eq!(
193 assert_eq!(
192 config.get_all(b"section", b"item"),
194 config.get_all(b"section", b"item"),
193 [b"value2", b"value1", b"value0"]
195 [b"value2", b"value1", b"value0"]
194 );
196 );
195 }
197 }
196 }
198 }
@@ -1,263 +1,253
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::utils::files::{
11 use crate::utils::files::{
11 get_bytes_from_path, get_path_from_bytes, read_whole_file,
12 get_bytes_from_path, get_path_from_bytes, read_whole_file,
12 };
13 };
13 use format_bytes::format_bytes;
14 use format_bytes::format_bytes;
14 use lazy_static::lazy_static;
15 use lazy_static::lazy_static;
15 use regex::bytes::Regex;
16 use regex::bytes::Regex;
16 use std::collections::HashMap;
17 use std::collections::HashMap;
17 use std::io;
18 use std::io;
18 use std::path::{Path, PathBuf};
19 use std::path::{Path, PathBuf};
19
20
20 lazy_static! {
21 lazy_static! {
21 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
22 static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
22 static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
23 static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
23 /// Continuation whitespace
24 /// Continuation whitespace
24 static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
25 static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
25 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
26 static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
26 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
27 static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
27 /// A directive that allows for removing previous entries
28 /// A directive that allows for removing previous entries
28 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
29 static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
29 /// A directive that allows for including other config files
30 /// A directive that allows for including other config files
30 static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
31 static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
31 }
32 }
32
33
33 /// All config values separated by layers of precedence.
34 /// All config values separated by layers of precedence.
34 /// Each config source may be split in multiple layers if `%include` directives
35 /// Each config source may be split in multiple layers if `%include` directives
35 /// are used.
36 /// are used.
36 /// TODO detail the general precedence
37 /// TODO detail the general precedence
37 #[derive(Clone)]
38 #[derive(Clone)]
38 pub struct ConfigLayer {
39 pub struct ConfigLayer {
39 /// Mapping of the sections to their items
40 /// Mapping of the sections to their items
40 sections: HashMap<Vec<u8>, ConfigItem>,
41 sections: HashMap<Vec<u8>, ConfigItem>,
41 /// All sections (and their items/values) in a layer share the same origin
42 /// All sections (and their items/values) in a layer share the same origin
42 pub origin: ConfigOrigin,
43 pub origin: ConfigOrigin,
43 /// Whether this layer comes from a trusted user or group
44 /// Whether this layer comes from a trusted user or group
44 pub trusted: bool,
45 pub trusted: bool,
45 }
46 }
46
47
47 impl ConfigLayer {
48 impl ConfigLayer {
48 pub fn new(origin: ConfigOrigin) -> Self {
49 pub fn new(origin: ConfigOrigin) -> Self {
49 ConfigLayer {
50 ConfigLayer {
50 sections: HashMap::new(),
51 sections: HashMap::new(),
51 trusted: true, // TODO check
52 trusted: true, // TODO check
52 origin,
53 origin,
53 }
54 }
54 }
55 }
55
56
56 /// Add an entry to the config, overwriting the old one if already present.
57 /// Add an entry to the config, overwriting the old one if already present.
57 pub fn add(
58 pub fn add(
58 &mut self,
59 &mut self,
59 section: Vec<u8>,
60 section: Vec<u8>,
60 item: Vec<u8>,
61 item: Vec<u8>,
61 value: Vec<u8>,
62 value: Vec<u8>,
62 line: Option<usize>,
63 line: Option<usize>,
63 ) {
64 ) {
64 self.sections
65 self.sections
65 .entry(section)
66 .entry(section)
66 .or_insert_with(|| HashMap::new())
67 .or_insert_with(|| HashMap::new())
67 .insert(item, ConfigValue { bytes: value, line });
68 .insert(item, ConfigValue { bytes: value, line });
68 }
69 }
69
70
70 /// Returns the config value in `<section>.<item>` if it exists
71 /// Returns the config value in `<section>.<item>` if it exists
71 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
72 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
72 Some(self.sections.get(section)?.get(item)?)
73 Some(self.sections.get(section)?.get(item)?)
73 }
74 }
74
75
75 pub fn is_empty(&self) -> bool {
76 pub fn is_empty(&self) -> bool {
76 self.sections.is_empty()
77 self.sections.is_empty()
77 }
78 }
78
79
79 /// Returns a `Vec` of layers in order of precedence (so, in read order),
80 /// Returns a `Vec` of layers in order of precedence (so, in read order),
80 /// recursively parsing the `%include` directives if any.
81 /// recursively parsing the `%include` directives if any.
81 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
82 pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
82 let mut layers = vec![];
83 let mut layers = vec![];
83
84
84 // Discard byte order mark if any
85 // Discard byte order mark if any
85 let data = if data.starts_with(b"\xef\xbb\xbf") {
86 let data = if data.starts_with(b"\xef\xbb\xbf") {
86 &data[3..]
87 &data[3..]
87 } else {
88 } else {
88 data
89 data
89 };
90 };
90
91
91 // TODO check if it's trusted
92 // TODO check if it's trusted
92 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
93 let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
93
94
94 let mut lines_iter =
95 let mut lines_iter =
95 data.split(|b| *b == b'\n').enumerate().peekable();
96 data.split(|b| *b == b'\n').enumerate().peekable();
96 let mut section = b"".to_vec();
97 let mut section = b"".to_vec();
97
98
98 while let Some((index, bytes)) = lines_iter.next() {
99 while let Some((index, bytes)) = lines_iter.next() {
99 if let Some(m) = INCLUDE_RE.captures(&bytes) {
100 if let Some(m) = INCLUDE_RE.captures(&bytes) {
100 let filename_bytes = &m[1];
101 let filename_bytes = &m[1];
101 let filename_to_include = get_path_from_bytes(&filename_bytes);
102 let filename_to_include = get_path_from_bytes(&filename_bytes);
102 match read_include(&src, &filename_to_include) {
103 let (include_src, result) =
103 (include_src, Ok(data)) => {
104 read_include(&src, &filename_to_include);
104 layers.push(current_layer);
105 let data = result.for_file(filename_to_include)?;
105 layers.extend(Self::parse(&include_src, &data)?);
106 layers.push(current_layer);
106 current_layer =
107 layers.extend(Self::parse(&include_src, &data)?);
107 Self::new(ConfigOrigin::File(src.to_owned()));
108 current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
108 }
109 (_, Err(e)) => {
110 return Err(ConfigError::IncludeError {
111 path: filename_to_include.to_owned(),
112 io_error: e,
113 })
114 }
115 }
116 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
109 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
117 } else if let Some(m) = SECTION_RE.captures(&bytes) {
110 } else if let Some(m) = SECTION_RE.captures(&bytes) {
118 section = m[1].to_vec();
111 section = m[1].to_vec();
119 } else if let Some(m) = ITEM_RE.captures(&bytes) {
112 } else if let Some(m) = ITEM_RE.captures(&bytes) {
120 let item = m[1].to_vec();
113 let item = m[1].to_vec();
121 let mut value = m[2].to_vec();
114 let mut value = m[2].to_vec();
122 loop {
115 loop {
123 match lines_iter.peek() {
116 match lines_iter.peek() {
124 None => break,
117 None => break,
125 Some((_, v)) => {
118 Some((_, v)) => {
126 if let Some(_) = COMMENT_RE.captures(&v) {
119 if let Some(_) = COMMENT_RE.captures(&v) {
127 } else if let Some(_) = CONT_RE.captures(&v) {
120 } else if let Some(_) = CONT_RE.captures(&v) {
128 value.extend(b"\n");
121 value.extend(b"\n");
129 value.extend(&m[1]);
122 value.extend(&m[1]);
130 } else {
123 } else {
131 break;
124 break;
132 }
125 }
133 }
126 }
134 };
127 };
135 lines_iter.next();
128 lines_iter.next();
136 }
129 }
137 current_layer.add(
130 current_layer.add(
138 section.clone(),
131 section.clone(),
139 item,
132 item,
140 value,
133 value,
141 Some(index + 1),
134 Some(index + 1),
142 );
135 );
143 } else if let Some(m) = UNSET_RE.captures(&bytes) {
136 } else if let Some(m) = UNSET_RE.captures(&bytes) {
144 if let Some(map) = current_layer.sections.get_mut(&section) {
137 if let Some(map) = current_layer.sections.get_mut(&section) {
145 map.remove(&m[1]);
138 map.remove(&m[1]);
146 }
139 }
147 } else {
140 } else {
148 return Err(ConfigError::Parse {
141 return Err(ConfigParseError {
149 origin: ConfigOrigin::File(src.to_owned()),
142 origin: ConfigOrigin::File(src.to_owned()),
150 line: Some(index + 1),
143 line: Some(index + 1),
151 bytes: bytes.to_owned(),
144 bytes: bytes.to_owned(),
152 });
145 }
146 .into());
153 }
147 }
154 }
148 }
155 if !current_layer.is_empty() {
149 if !current_layer.is_empty() {
156 layers.push(current_layer);
150 layers.push(current_layer);
157 }
151 }
158 Ok(layers)
152 Ok(layers)
159 }
153 }
160 }
154 }
161
155
162 impl std::fmt::Debug for ConfigLayer {
156 impl std::fmt::Debug for ConfigLayer {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 let mut sections: Vec<_> = self.sections.iter().collect();
158 let mut sections: Vec<_> = self.sections.iter().collect();
165 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
159 sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
166
160
167 for (section, items) in sections.into_iter() {
161 for (section, items) in sections.into_iter() {
168 let mut items: Vec<_> = items.into_iter().collect();
162 let mut items: Vec<_> = items.into_iter().collect();
169 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
163 items.sort_by(|e0, e1| e0.0.cmp(e1.0));
170
164
171 for (item, config_entry) in items {
165 for (item, config_entry) in items {
172 writeln!(
166 writeln!(
173 f,
167 f,
174 "{}",
168 "{}",
175 String::from_utf8_lossy(&format_bytes!(
169 String::from_utf8_lossy(&format_bytes!(
176 b"{}.{}={} # {}",
170 b"{}.{}={} # {}",
177 section,
171 section,
178 item,
172 item,
179 &config_entry.bytes,
173 &config_entry.bytes,
180 &self.origin.to_bytes(),
174 &self.origin.to_bytes(),
181 ))
175 ))
182 )?
176 )?
183 }
177 }
184 }
178 }
185 Ok(())
179 Ok(())
186 }
180 }
187 }
181 }
188
182
189 /// Mapping of section item to value.
183 /// Mapping of section item to value.
190 /// In the following:
184 /// In the following:
191 /// ```text
185 /// ```text
192 /// [ui]
186 /// [ui]
193 /// paginate=no
187 /// paginate=no
194 /// ```
188 /// ```
195 /// "paginate" is the section item and "no" the value.
189 /// "paginate" is the section item and "no" the value.
196 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
190 pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
197
191
198 #[derive(Clone, Debug, PartialEq)]
192 #[derive(Clone, Debug, PartialEq)]
199 pub struct ConfigValue {
193 pub struct ConfigValue {
200 /// The raw bytes of the value (be it from the CLI, env or from a file)
194 /// The raw bytes of the value (be it from the CLI, env or from a file)
201 pub bytes: Vec<u8>,
195 pub bytes: Vec<u8>,
202 /// Only present if the value comes from a file, 1-indexed.
196 /// Only present if the value comes from a file, 1-indexed.
203 pub line: Option<usize>,
197 pub line: Option<usize>,
204 }
198 }
205
199
206 #[derive(Clone, Debug)]
200 #[derive(Clone, Debug)]
207 pub enum ConfigOrigin {
201 pub enum ConfigOrigin {
208 /// The value comes from a configuration file
202 /// The value comes from a configuration file
209 File(PathBuf),
203 File(PathBuf),
210 /// The value comes from the environment like `$PAGER` or `$EDITOR`
204 /// The value comes from the environment like `$PAGER` or `$EDITOR`
211 Environment(Vec<u8>),
205 Environment(Vec<u8>),
212 /* TODO cli
206 /* TODO cli
213 * TODO defaults (configitems.py)
207 * TODO defaults (configitems.py)
214 * TODO extensions
208 * TODO extensions
215 * TODO Python resources?
209 * TODO Python resources?
216 * Others? */
210 * Others? */
217 }
211 }
218
212
219 impl ConfigOrigin {
213 impl ConfigOrigin {
220 /// TODO use some kind of dedicated trait?
214 /// TODO use some kind of dedicated trait?
221 pub fn to_bytes(&self) -> Vec<u8> {
215 pub fn to_bytes(&self) -> Vec<u8> {
222 match self {
216 match self {
223 ConfigOrigin::File(p) => get_bytes_from_path(p),
217 ConfigOrigin::File(p) => get_bytes_from_path(p),
224 ConfigOrigin::Environment(e) => e.to_owned(),
218 ConfigOrigin::Environment(e) => e.to_owned(),
225 }
219 }
226 }
220 }
227 }
221 }
228
222
223 #[derive(Debug)]
224 pub struct ConfigParseError {
225 pub origin: ConfigOrigin,
226 pub line: Option<usize>,
227 pub bytes: Vec<u8>,
228 }
229
229 #[derive(Debug, derive_more::From)]
230 #[derive(Debug, derive_more::From)]
230 pub enum ConfigError {
231 pub enum ConfigError {
231 Parse {
232 Parse(ConfigParseError),
232 origin: ConfigOrigin,
233 Other(HgError),
233 line: Option<usize>,
234 bytes: Vec<u8>,
235 },
236 /// Failed to include a sub config file
237 IncludeError {
238 path: PathBuf,
239 io_error: std::io::Error,
240 },
241 /// Any IO error that isn't expected
242 #[from]
243 IO(std::io::Error),
244 }
234 }
245
235
246 fn make_regex(pattern: &'static str) -> Regex {
236 fn make_regex(pattern: &'static str) -> Regex {
247 Regex::new(pattern).expect("expected a valid regex")
237 Regex::new(pattern).expect("expected a valid regex")
248 }
238 }
249
239
250 /// Includes are relative to the file they're defined in, unless they're
240 /// Includes are relative to the file they're defined in, unless they're
251 /// absolute.
241 /// absolute.
252 fn read_include(
242 fn read_include(
253 old_src: &Path,
243 old_src: &Path,
254 new_src: &Path,
244 new_src: &Path,
255 ) -> (PathBuf, io::Result<Vec<u8>>) {
245 ) -> (PathBuf, io::Result<Vec<u8>>) {
256 if new_src.is_absolute() {
246 if new_src.is_absolute() {
257 (new_src.to_path_buf(), read_whole_file(&new_src))
247 (new_src.to_path_buf(), read_whole_file(&new_src))
258 } else {
248 } else {
259 let dir = old_src.parent().unwrap();
249 let dir = old_src.parent().unwrap();
260 let new_src = dir.join(&new_src);
250 let new_src = dir.join(&new_src);
261 (new_src.to_owned(), read_whole_file(&new_src))
251 (new_src.to_owned(), read_whole_file(&new_src))
262 }
252 }
263 }
253 }
General Comments 0
You need to be logged in to leave comments. Login now