##// END OF EJS Templates
rust: box ConfigValueParseError to avoid large result types...
Arseniy Alekseyev -
r51104:af9d050f default
parent child Browse files
Show More
@@ -1,633 +1,636 b''
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 //! Mercurial config parsing and interfaces.
10 //! Mercurial config parsing and interfaces.
11
11
12 mod layer;
12 mod layer;
13 mod plain_info;
13 mod plain_info;
14 mod values;
14 mod values;
15 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
15 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
16 pub use plain_info::PlainInfo;
16 pub use plain_info::PlainInfo;
17
17
18 use self::layer::ConfigLayer;
18 use self::layer::ConfigLayer;
19 use self::layer::ConfigValue;
19 use self::layer::ConfigValue;
20 use crate::errors::{HgResultExt, IoResultExt};
20 use crate::errors::{HgResultExt, IoResultExt};
21 use crate::utils::files::get_bytes_from_os_str;
21 use crate::utils::files::get_bytes_from_os_str;
22 use format_bytes::{write_bytes, DisplayBytes};
22 use format_bytes::{write_bytes, DisplayBytes};
23 use std::collections::HashSet;
23 use std::collections::HashSet;
24 use std::env;
24 use std::env;
25 use std::fmt;
25 use std::fmt;
26 use std::path::{Path, PathBuf};
26 use std::path::{Path, PathBuf};
27 use std::str;
27 use std::str;
28
28
29 /// Holds the config values for the current repository
29 /// Holds the config values for the current repository
30 /// TODO update this docstring once we support more sources
30 /// TODO update this docstring once we support more sources
31 #[derive(Clone)]
31 #[derive(Clone)]
32 pub struct Config {
32 pub struct Config {
33 layers: Vec<layer::ConfigLayer>,
33 layers: Vec<layer::ConfigLayer>,
34 plain: PlainInfo,
34 plain: PlainInfo,
35 }
35 }
36
36
37 impl DisplayBytes for Config {
37 impl DisplayBytes for Config {
38 fn display_bytes(
38 fn display_bytes(
39 &self,
39 &self,
40 out: &mut dyn std::io::Write,
40 out: &mut dyn std::io::Write,
41 ) -> std::io::Result<()> {
41 ) -> std::io::Result<()> {
42 for (index, layer) in self.layers.iter().rev().enumerate() {
42 for (index, layer) in self.layers.iter().rev().enumerate() {
43 write_bytes!(
43 write_bytes!(
44 out,
44 out,
45 b"==== Layer {} (trusted: {}) ====\n{}",
45 b"==== Layer {} (trusted: {}) ====\n{}",
46 index,
46 index,
47 if layer.trusted {
47 if layer.trusted {
48 &b"yes"[..]
48 &b"yes"[..]
49 } else {
49 } else {
50 &b"no"[..]
50 &b"no"[..]
51 },
51 },
52 layer
52 layer
53 )?;
53 )?;
54 }
54 }
55 Ok(())
55 Ok(())
56 }
56 }
57 }
57 }
58
58
59 pub enum ConfigSource {
59 pub enum ConfigSource {
60 /// Absolute path to a config file
60 /// Absolute path to a config file
61 AbsPath(PathBuf),
61 AbsPath(PathBuf),
62 /// Already parsed (from the CLI, env, Python resources, etc.)
62 /// Already parsed (from the CLI, env, Python resources, etc.)
63 Parsed(layer::ConfigLayer),
63 Parsed(layer::ConfigLayer),
64 }
64 }
65
65
66 #[derive(Debug)]
66 #[derive(Debug)]
67 pub struct ConfigValueParseError {
67 pub struct ConfigValueParseErrorDetails {
68 pub origin: ConfigOrigin,
68 pub origin: ConfigOrigin,
69 pub line: Option<usize>,
69 pub line: Option<usize>,
70 pub section: Vec<u8>,
70 pub section: Vec<u8>,
71 pub item: Vec<u8>,
71 pub item: Vec<u8>,
72 pub value: Vec<u8>,
72 pub value: Vec<u8>,
73 pub expected_type: &'static str,
73 pub expected_type: &'static str,
74 }
74 }
75
75
76 // boxed to avoid very large Result types
77 pub type ConfigValueParseError = Box<ConfigValueParseErrorDetails>;
78
76 impl fmt::Display for ConfigValueParseError {
79 impl fmt::Display for ConfigValueParseError {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 // TODO: add origin and line number information, here and in
81 // TODO: add origin and line number information, here and in
79 // corresponding python code
82 // corresponding python code
80 write!(
83 write!(
81 f,
84 f,
82 "config error: {}.{} is not a {} ('{}')",
85 "config error: {}.{} is not a {} ('{}')",
83 String::from_utf8_lossy(&self.section),
86 String::from_utf8_lossy(&self.section),
84 String::from_utf8_lossy(&self.item),
87 String::from_utf8_lossy(&self.item),
85 self.expected_type,
88 self.expected_type,
86 String::from_utf8_lossy(&self.value)
89 String::from_utf8_lossy(&self.value)
87 )
90 )
88 }
91 }
89 }
92 }
90
93
91 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
94 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
92 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
95 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
93 // duplication with [_applyconfig] in [ui.py],
96 // duplication with [_applyconfig] in [ui.py],
94 if !plain.is_plain() {
97 if !plain.is_plain() {
95 return false;
98 return false;
96 }
99 }
97 if section == b"alias" {
100 if section == b"alias" {
98 return plain.plainalias();
101 return plain.plainalias();
99 }
102 }
100 if section == b"revsetalias" {
103 if section == b"revsetalias" {
101 return plain.plainrevsetalias();
104 return plain.plainrevsetalias();
102 }
105 }
103 if section == b"templatealias" {
106 if section == b"templatealias" {
104 return plain.plaintemplatealias();
107 return plain.plaintemplatealias();
105 }
108 }
106 if section == b"ui" {
109 if section == b"ui" {
107 let to_delete: &[&[u8]] = &[
110 let to_delete: &[&[u8]] = &[
108 b"debug",
111 b"debug",
109 b"fallbackencoding",
112 b"fallbackencoding",
110 b"quiet",
113 b"quiet",
111 b"slash",
114 b"slash",
112 b"logtemplate",
115 b"logtemplate",
113 b"message-output",
116 b"message-output",
114 b"statuscopies",
117 b"statuscopies",
115 b"style",
118 b"style",
116 b"traceback",
119 b"traceback",
117 b"verbose",
120 b"verbose",
118 ];
121 ];
119 return to_delete.contains(&item);
122 return to_delete.contains(&item);
120 }
123 }
121 let sections_to_delete: &[&[u8]] =
124 let sections_to_delete: &[&[u8]] =
122 &[b"defaults", b"commands", b"command-templates"];
125 &[b"defaults", b"commands", b"command-templates"];
123 sections_to_delete.contains(&section)
126 sections_to_delete.contains(&section)
124 }
127 }
125
128
126 impl Config {
129 impl Config {
127 /// The configuration to use when printing configuration-loading errors
130 /// The configuration to use when printing configuration-loading errors
128 pub fn empty() -> Self {
131 pub fn empty() -> Self {
129 Self {
132 Self {
130 layers: Vec::new(),
133 layers: Vec::new(),
131 plain: PlainInfo::empty(),
134 plain: PlainInfo::empty(),
132 }
135 }
133 }
136 }
134
137
135 /// Load system and user configuration from various files.
138 /// Load system and user configuration from various files.
136 ///
139 ///
137 /// This is also affected by some environment variables.
140 /// This is also affected by some environment variables.
138 pub fn load_non_repo() -> Result<Self, ConfigError> {
141 pub fn load_non_repo() -> Result<Self, ConfigError> {
139 let mut config = Self::empty();
142 let mut config = Self::empty();
140 let opt_rc_path = env::var_os("HGRCPATH");
143 let opt_rc_path = env::var_os("HGRCPATH");
141 // HGRCPATH replaces system config
144 // HGRCPATH replaces system config
142 if opt_rc_path.is_none() {
145 if opt_rc_path.is_none() {
143 config.add_system_config()?
146 config.add_system_config()?
144 }
147 }
145
148
146 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
149 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
147 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
150 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
148 config.add_for_environment_variable("PAGER", b"pager", b"pager");
151 config.add_for_environment_variable("PAGER", b"pager", b"pager");
149
152
150 // These are set by `run-tests.py --rhg` to enable fallback for the
153 // These are set by `run-tests.py --rhg` to enable fallback for the
151 // entire test suite. Alternatives would be setting configuration
154 // entire test suite. Alternatives would be setting configuration
152 // through `$HGRCPATH` but some tests override that, or changing the
155 // through `$HGRCPATH` but some tests override that, or changing the
153 // `hg` shell alias to include `--config` but that disrupts tests that
156 // `hg` shell alias to include `--config` but that disrupts tests that
154 // print command lines and check expected output.
157 // print command lines and check expected output.
155 config.add_for_environment_variable(
158 config.add_for_environment_variable(
156 "RHG_ON_UNSUPPORTED",
159 "RHG_ON_UNSUPPORTED",
157 b"rhg",
160 b"rhg",
158 b"on-unsupported",
161 b"on-unsupported",
159 );
162 );
160 config.add_for_environment_variable(
163 config.add_for_environment_variable(
161 "RHG_FALLBACK_EXECUTABLE",
164 "RHG_FALLBACK_EXECUTABLE",
162 b"rhg",
165 b"rhg",
163 b"fallback-executable",
166 b"fallback-executable",
164 );
167 );
165
168
166 // HGRCPATH replaces user config
169 // HGRCPATH replaces user config
167 if opt_rc_path.is_none() {
170 if opt_rc_path.is_none() {
168 config.add_user_config()?
171 config.add_user_config()?
169 }
172 }
170 if let Some(rc_path) = &opt_rc_path {
173 if let Some(rc_path) = &opt_rc_path {
171 for path in env::split_paths(rc_path) {
174 for path in env::split_paths(rc_path) {
172 if !path.as_os_str().is_empty() {
175 if !path.as_os_str().is_empty() {
173 if path.is_dir() {
176 if path.is_dir() {
174 config.add_trusted_dir(&path)?
177 config.add_trusted_dir(&path)?
175 } else {
178 } else {
176 config.add_trusted_file(&path)?
179 config.add_trusted_file(&path)?
177 }
180 }
178 }
181 }
179 }
182 }
180 }
183 }
181 Ok(config)
184 Ok(config)
182 }
185 }
183
186
184 pub fn load_cli_args(
187 pub fn load_cli_args(
185 &mut self,
188 &mut self,
186 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
189 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
187 color_arg: Option<Vec<u8>>,
190 color_arg: Option<Vec<u8>>,
188 ) -> Result<(), ConfigError> {
191 ) -> Result<(), ConfigError> {
189 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
192 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
190 self.layers.push(layer)
193 self.layers.push(layer)
191 }
194 }
192 if let Some(arg) = color_arg {
195 if let Some(arg) = color_arg {
193 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
196 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
194 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
197 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
195 self.layers.push(layer)
198 self.layers.push(layer)
196 }
199 }
197 Ok(())
200 Ok(())
198 }
201 }
199
202
200 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
203 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
201 if let Some(entries) = std::fs::read_dir(path)
204 if let Some(entries) = std::fs::read_dir(path)
202 .when_reading_file(path)
205 .when_reading_file(path)
203 .io_not_found_as_none()?
206 .io_not_found_as_none()?
204 {
207 {
205 let mut file_paths = entries
208 let mut file_paths = entries
206 .map(|result| {
209 .map(|result| {
207 result.when_reading_file(path).map(|entry| entry.path())
210 result.when_reading_file(path).map(|entry| entry.path())
208 })
211 })
209 .collect::<Result<Vec<_>, _>>()?;
212 .collect::<Result<Vec<_>, _>>()?;
210 file_paths.sort();
213 file_paths.sort();
211 for file_path in &file_paths {
214 for file_path in &file_paths {
212 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
215 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
213 self.add_trusted_file(file_path)?
216 self.add_trusted_file(file_path)?
214 }
217 }
215 }
218 }
216 }
219 }
217 Ok(())
220 Ok(())
218 }
221 }
219
222
220 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
223 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
221 if let Some(data) = std::fs::read(path)
224 if let Some(data) = std::fs::read(path)
222 .when_reading_file(path)
225 .when_reading_file(path)
223 .io_not_found_as_none()?
226 .io_not_found_as_none()?
224 {
227 {
225 self.layers.extend(ConfigLayer::parse(path, &data)?)
228 self.layers.extend(ConfigLayer::parse(path, &data)?)
226 }
229 }
227 Ok(())
230 Ok(())
228 }
231 }
229
232
230 fn add_for_environment_variable(
233 fn add_for_environment_variable(
231 &mut self,
234 &mut self,
232 var: &str,
235 var: &str,
233 section: &[u8],
236 section: &[u8],
234 key: &[u8],
237 key: &[u8],
235 ) {
238 ) {
236 if let Some(value) = env::var_os(var) {
239 if let Some(value) = env::var_os(var) {
237 let origin = layer::ConfigOrigin::Environment(var.into());
240 let origin = layer::ConfigOrigin::Environment(var.into());
238 let mut layer = ConfigLayer::new(origin);
241 let mut layer = ConfigLayer::new(origin);
239 layer.add(
242 layer.add(
240 section.to_owned(),
243 section.to_owned(),
241 key.to_owned(),
244 key.to_owned(),
242 get_bytes_from_os_str(value),
245 get_bytes_from_os_str(value),
243 None,
246 None,
244 );
247 );
245 self.layers.push(layer)
248 self.layers.push(layer)
246 }
249 }
247 }
250 }
248
251
249 #[cfg(unix)] // TODO: other platforms
252 #[cfg(unix)] // TODO: other platforms
250 fn add_system_config(&mut self) -> Result<(), ConfigError> {
253 fn add_system_config(&mut self) -> Result<(), ConfigError> {
251 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
254 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
252 let etc = prefix.join("etc").join("mercurial");
255 let etc = prefix.join("etc").join("mercurial");
253 self.add_trusted_file(&etc.join("hgrc"))?;
256 self.add_trusted_file(&etc.join("hgrc"))?;
254 self.add_trusted_dir(&etc.join("hgrc.d"))
257 self.add_trusted_dir(&etc.join("hgrc.d"))
255 };
258 };
256 let root = Path::new("/");
259 let root = Path::new("/");
257 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
260 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
258 // instead? TODO: can this be a relative path?
261 // instead? TODO: can this be a relative path?
259 let hg = crate::utils::current_exe()?;
262 let hg = crate::utils::current_exe()?;
260 // TODO: this order (per-installation then per-system) matches
263 // TODO: this order (per-installation then per-system) matches
261 // `systemrcpath()` in `mercurial/scmposix.py`, but
264 // `systemrcpath()` in `mercurial/scmposix.py`, but
262 // `mercurial/helptext/config.txt` suggests it should be reversed
265 // `mercurial/helptext/config.txt` suggests it should be reversed
263 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
266 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
264 if installation_prefix != root {
267 if installation_prefix != root {
265 add_for_prefix(installation_prefix)?
268 add_for_prefix(installation_prefix)?
266 }
269 }
267 }
270 }
268 add_for_prefix(root)?;
271 add_for_prefix(root)?;
269 Ok(())
272 Ok(())
270 }
273 }
271
274
272 #[cfg(unix)] // TODO: other plateforms
275 #[cfg(unix)] // TODO: other plateforms
273 fn add_user_config(&mut self) -> Result<(), ConfigError> {
276 fn add_user_config(&mut self) -> Result<(), ConfigError> {
274 let opt_home = home::home_dir();
277 let opt_home = home::home_dir();
275 if let Some(home) = &opt_home {
278 if let Some(home) = &opt_home {
276 self.add_trusted_file(&home.join(".hgrc"))?
279 self.add_trusted_file(&home.join(".hgrc"))?
277 }
280 }
278 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
281 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
279 if !darwin {
282 if !darwin {
280 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
283 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
281 .map(PathBuf::from)
284 .map(PathBuf::from)
282 .or_else(|| opt_home.map(|home| home.join(".config")))
285 .or_else(|| opt_home.map(|home| home.join(".config")))
283 {
286 {
284 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
287 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
285 }
288 }
286 }
289 }
287 Ok(())
290 Ok(())
288 }
291 }
289
292
290 /// Loads in order, which means that the precedence is the same
293 /// Loads in order, which means that the precedence is the same
291 /// as the order of `sources`.
294 /// as the order of `sources`.
292 pub fn load_from_explicit_sources(
295 pub fn load_from_explicit_sources(
293 sources: Vec<ConfigSource>,
296 sources: Vec<ConfigSource>,
294 ) -> Result<Self, ConfigError> {
297 ) -> Result<Self, ConfigError> {
295 let mut layers = vec![];
298 let mut layers = vec![];
296
299
297 for source in sources.into_iter() {
300 for source in sources.into_iter() {
298 match source {
301 match source {
299 ConfigSource::Parsed(c) => layers.push(c),
302 ConfigSource::Parsed(c) => layers.push(c),
300 ConfigSource::AbsPath(c) => {
303 ConfigSource::AbsPath(c) => {
301 // TODO check if it should be trusted
304 // TODO check if it should be trusted
302 // mercurial/ui.py:427
305 // mercurial/ui.py:427
303 let data = match std::fs::read(&c) {
306 let data = match std::fs::read(&c) {
304 Err(_) => continue, // same as the python code
307 Err(_) => continue, // same as the python code
305 Ok(data) => data,
308 Ok(data) => data,
306 };
309 };
307 layers.extend(ConfigLayer::parse(&c, &data)?)
310 layers.extend(ConfigLayer::parse(&c, &data)?)
308 }
311 }
309 }
312 }
310 }
313 }
311
314
312 Ok(Config {
315 Ok(Config {
313 layers,
316 layers,
314 plain: PlainInfo::empty(),
317 plain: PlainInfo::empty(),
315 })
318 })
316 }
319 }
317
320
318 /// Loads the per-repository config into a new `Config` which is combined
321 /// Loads the per-repository config into a new `Config` which is combined
319 /// with `self`.
322 /// with `self`.
320 pub(crate) fn combine_with_repo(
323 pub(crate) fn combine_with_repo(
321 &self,
324 &self,
322 repo_config_files: &[PathBuf],
325 repo_config_files: &[PathBuf],
323 ) -> Result<Self, ConfigError> {
326 ) -> Result<Self, ConfigError> {
324 let (cli_layers, other_layers) = self
327 let (cli_layers, other_layers) = self
325 .layers
328 .layers
326 .iter()
329 .iter()
327 .cloned()
330 .cloned()
328 .partition(ConfigLayer::is_from_command_line);
331 .partition(ConfigLayer::is_from_command_line);
329
332
330 let mut repo_config = Self {
333 let mut repo_config = Self {
331 layers: other_layers,
334 layers: other_layers,
332 plain: PlainInfo::empty(),
335 plain: PlainInfo::empty(),
333 };
336 };
334 for path in repo_config_files {
337 for path in repo_config_files {
335 // TODO: check if this file should be trusted:
338 // TODO: check if this file should be trusted:
336 // `mercurial/ui.py:427`
339 // `mercurial/ui.py:427`
337 repo_config.add_trusted_file(path)?;
340 repo_config.add_trusted_file(path)?;
338 }
341 }
339 repo_config.layers.extend(cli_layers);
342 repo_config.layers.extend(cli_layers);
340 Ok(repo_config)
343 Ok(repo_config)
341 }
344 }
342
345
343 pub fn apply_plain(&mut self, plain: PlainInfo) {
346 pub fn apply_plain(&mut self, plain: PlainInfo) {
344 self.plain = plain;
347 self.plain = plain;
345 }
348 }
346
349
347 fn get_parse<'config, T: 'config>(
350 fn get_parse<'config, T: 'config>(
348 &'config self,
351 &'config self,
349 section: &[u8],
352 section: &[u8],
350 item: &[u8],
353 item: &[u8],
351 expected_type: &'static str,
354 expected_type: &'static str,
352 parse: impl Fn(&'config [u8]) -> Option<T>,
355 parse: impl Fn(&'config [u8]) -> Option<T>,
353 ) -> Result<Option<T>, ConfigValueParseError> {
356 ) -> Result<Option<T>, ConfigValueParseError> {
354 match self.get_inner(section, item) {
357 match self.get_inner(section, item) {
355 Some((layer, v)) => match parse(&v.bytes) {
358 Some((layer, v)) => match parse(&v.bytes) {
356 Some(b) => Ok(Some(b)),
359 Some(b) => Ok(Some(b)),
357 None => Err(ConfigValueParseError {
360 None => Err(Box::new(ConfigValueParseErrorDetails {
358 origin: layer.origin.to_owned(),
361 origin: layer.origin.to_owned(),
359 line: v.line,
362 line: v.line,
360 value: v.bytes.to_owned(),
363 value: v.bytes.to_owned(),
361 section: section.to_owned(),
364 section: section.to_owned(),
362 item: item.to_owned(),
365 item: item.to_owned(),
363 expected_type,
366 expected_type,
364 }),
367 })),
365 },
368 },
366 None => Ok(None),
369 None => Ok(None),
367 }
370 }
368 }
371 }
369
372
370 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
373 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
371 /// Otherwise, returns an `Ok(value)` if found, or `None`.
374 /// Otherwise, returns an `Ok(value)` if found, or `None`.
372 pub fn get_str(
375 pub fn get_str(
373 &self,
376 &self,
374 section: &[u8],
377 section: &[u8],
375 item: &[u8],
378 item: &[u8],
376 ) -> Result<Option<&str>, ConfigValueParseError> {
379 ) -> Result<Option<&str>, ConfigValueParseError> {
377 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
380 self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
378 str::from_utf8(value).ok()
381 str::from_utf8(value).ok()
379 })
382 })
380 }
383 }
381
384
382 /// Returns an `Err` if the first value found is not a valid unsigned
385 /// Returns an `Err` if the first value found is not a valid unsigned
383 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
386 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
384 pub fn get_u32(
387 pub fn get_u32(
385 &self,
388 &self,
386 section: &[u8],
389 section: &[u8],
387 item: &[u8],
390 item: &[u8],
388 ) -> Result<Option<u32>, ConfigValueParseError> {
391 ) -> Result<Option<u32>, ConfigValueParseError> {
389 self.get_parse(section, item, "valid integer", |value| {
392 self.get_parse(section, item, "valid integer", |value| {
390 str::from_utf8(value).ok()?.parse().ok()
393 str::from_utf8(value).ok()?.parse().ok()
391 })
394 })
392 }
395 }
393
396
394 /// Returns an `Err` if the first value found is not a valid file size
397 /// Returns an `Err` if the first value found is not a valid file size
395 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
398 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
396 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
399 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
397 pub fn get_byte_size(
400 pub fn get_byte_size(
398 &self,
401 &self,
399 section: &[u8],
402 section: &[u8],
400 item: &[u8],
403 item: &[u8],
401 ) -> Result<Option<u64>, ConfigValueParseError> {
404 ) -> Result<Option<u64>, ConfigValueParseError> {
402 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
405 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
403 }
406 }
404
407
405 /// Returns an `Err` if the first value found is not a valid boolean.
408 /// Returns an `Err` if the first value found is not a valid boolean.
406 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
409 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
407 /// found, or `None`.
410 /// found, or `None`.
408 pub fn get_option(
411 pub fn get_option(
409 &self,
412 &self,
410 section: &[u8],
413 section: &[u8],
411 item: &[u8],
414 item: &[u8],
412 ) -> Result<Option<bool>, ConfigValueParseError> {
415 ) -> Result<Option<bool>, ConfigValueParseError> {
413 self.get_parse(section, item, "boolean", values::parse_bool)
416 self.get_parse(section, item, "boolean", values::parse_bool)
414 }
417 }
415
418
416 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
419 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
417 /// if the value is not found, an `Err` if it's not a valid boolean.
420 /// if the value is not found, an `Err` if it's not a valid boolean.
418 pub fn get_bool(
421 pub fn get_bool(
419 &self,
422 &self,
420 section: &[u8],
423 section: &[u8],
421 item: &[u8],
424 item: &[u8],
422 ) -> Result<bool, ConfigValueParseError> {
425 ) -> Result<bool, ConfigValueParseError> {
423 Ok(self.get_option(section, item)?.unwrap_or(false))
426 Ok(self.get_option(section, item)?.unwrap_or(false))
424 }
427 }
425
428
426 /// Returns `true` if the extension is enabled, `false` otherwise
429 /// Returns `true` if the extension is enabled, `false` otherwise
427 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
430 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
428 let value = self.get(b"extensions", extension);
431 let value = self.get(b"extensions", extension);
429 match value {
432 match value {
430 Some(c) => !c.starts_with(b"!"),
433 Some(c) => !c.starts_with(b"!"),
431 None => false,
434 None => false,
432 }
435 }
433 }
436 }
434
437
435 /// If there is an `item` value in `section`, parse and return a list of
438 /// If there is an `item` value in `section`, parse and return a list of
436 /// byte strings.
439 /// byte strings.
437 pub fn get_list(
440 pub fn get_list(
438 &self,
441 &self,
439 section: &[u8],
442 section: &[u8],
440 item: &[u8],
443 item: &[u8],
441 ) -> Option<Vec<Vec<u8>>> {
444 ) -> Option<Vec<Vec<u8>>> {
442 self.get(section, item).map(values::parse_list)
445 self.get(section, item).map(values::parse_list)
443 }
446 }
444
447
445 /// Returns the raw value bytes of the first one found, or `None`.
448 /// Returns the raw value bytes of the first one found, or `None`.
446 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
449 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
447 self.get_inner(section, item)
450 self.get_inner(section, item)
448 .map(|(_, value)| value.bytes.as_ref())
451 .map(|(_, value)| value.bytes.as_ref())
449 }
452 }
450
453
451 /// Returns the raw value bytes of the first one found, or `None`.
454 /// Returns the raw value bytes of the first one found, or `None`.
452 pub fn get_with_origin(
455 pub fn get_with_origin(
453 &self,
456 &self,
454 section: &[u8],
457 section: &[u8],
455 item: &[u8],
458 item: &[u8],
456 ) -> Option<(&[u8], &ConfigOrigin)> {
459 ) -> Option<(&[u8], &ConfigOrigin)> {
457 self.get_inner(section, item)
460 self.get_inner(section, item)
458 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
461 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
459 }
462 }
460
463
461 /// Returns the layer and the value of the first one found, or `None`.
464 /// Returns the layer and the value of the first one found, or `None`.
462 fn get_inner(
465 fn get_inner(
463 &self,
466 &self,
464 section: &[u8],
467 section: &[u8],
465 item: &[u8],
468 item: &[u8],
466 ) -> Option<(&ConfigLayer, &ConfigValue)> {
469 ) -> Option<(&ConfigLayer, &ConfigValue)> {
467 // Filter out the config items that are hidden by [PLAIN].
470 // Filter out the config items that are hidden by [PLAIN].
468 // This differs from python hg where we delete them from the config.
471 // This differs from python hg where we delete them from the config.
469 let should_ignore = should_ignore(&self.plain, section, item);
472 let should_ignore = should_ignore(&self.plain, section, item);
470 for layer in self.layers.iter().rev() {
473 for layer in self.layers.iter().rev() {
471 if !layer.trusted {
474 if !layer.trusted {
472 continue;
475 continue;
473 }
476 }
474 //The [PLAIN] config should not affect the defaults.
477 //The [PLAIN] config should not affect the defaults.
475 //
478 //
476 // However, PLAIN should also affect the "tweaked" defaults (unless
479 // However, PLAIN should also affect the "tweaked" defaults (unless
477 // "tweakdefault" is part of "HGPLAINEXCEPT").
480 // "tweakdefault" is part of "HGPLAINEXCEPT").
478 //
481 //
479 // In practice the tweak-default layer is only added when it is
482 // In practice the tweak-default layer is only added when it is
480 // relevant, so we can safely always take it into
483 // relevant, so we can safely always take it into
481 // account here.
484 // account here.
482 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
485 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
483 {
486 {
484 continue;
487 continue;
485 }
488 }
486 if let Some(v) = layer.get(section, item) {
489 if let Some(v) = layer.get(section, item) {
487 return Some((layer, v));
490 return Some((layer, v));
488 }
491 }
489 }
492 }
490 None
493 None
491 }
494 }
492
495
493 /// Return all keys defined for the given section
496 /// Return all keys defined for the given section
494 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
497 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
495 self.layers
498 self.layers
496 .iter()
499 .iter()
497 .flat_map(|layer| layer.iter_keys(section))
500 .flat_map(|layer| layer.iter_keys(section))
498 .collect()
501 .collect()
499 }
502 }
500
503
501 /// Returns whether any key is defined in the given section
504 /// Returns whether any key is defined in the given section
502 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
505 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
503 self.layers
506 self.layers
504 .iter()
507 .iter()
505 .any(|layer| layer.has_non_empty_section(section))
508 .any(|layer| layer.has_non_empty_section(section))
506 }
509 }
507
510
508 /// Yields (key, value) pairs for everything in the given section
511 /// Yields (key, value) pairs for everything in the given section
509 pub fn iter_section<'a>(
512 pub fn iter_section<'a>(
510 &'a self,
513 &'a self,
511 section: &'a [u8],
514 section: &'a [u8],
512 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
515 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
513 // Deduplicate keys redefined in multiple layers
516 // Deduplicate keys redefined in multiple layers
514 let mut keys_already_seen = HashSet::new();
517 let mut keys_already_seen = HashSet::new();
515 let mut key_is_new =
518 let mut key_is_new =
516 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
519 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
517 keys_already_seen.insert(key)
520 keys_already_seen.insert(key)
518 };
521 };
519 // This is similar to `flat_map` + `filter_map`, except with a single
522 // This is similar to `flat_map` + `filter_map`, except with a single
520 // closure that owns `key_is_new` (and therefore the
523 // closure that owns `key_is_new` (and therefore the
521 // `keys_already_seen` set):
524 // `keys_already_seen` set):
522 let mut layer_iters = self
525 let mut layer_iters = self
523 .layers
526 .layers
524 .iter()
527 .iter()
525 .rev()
528 .rev()
526 .map(move |layer| layer.iter_section(section))
529 .map(move |layer| layer.iter_section(section))
527 .peekable();
530 .peekable();
528 std::iter::from_fn(move || loop {
531 std::iter::from_fn(move || loop {
529 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
532 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
530 return Some(pair);
533 return Some(pair);
531 } else {
534 } else {
532 layer_iters.next();
535 layer_iters.next();
533 }
536 }
534 })
537 })
535 }
538 }
536
539
537 /// Get raw values bytes from all layers (even untrusted ones) in order
540 /// Get raw values bytes from all layers (even untrusted ones) in order
538 /// of precedence.
541 /// of precedence.
539 #[cfg(test)]
542 #[cfg(test)]
540 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
543 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
541 let mut res = vec![];
544 let mut res = vec![];
542 for layer in self.layers.iter().rev() {
545 for layer in self.layers.iter().rev() {
543 if let Some(v) = layer.get(section, item) {
546 if let Some(v) = layer.get(section, item) {
544 res.push(v.bytes.as_ref());
547 res.push(v.bytes.as_ref());
545 }
548 }
546 }
549 }
547 res
550 res
548 }
551 }
549
552
550 // a config layer that's introduced by ui.tweakdefaults
553 // a config layer that's introduced by ui.tweakdefaults
551 fn tweakdefaults_layer() -> ConfigLayer {
554 fn tweakdefaults_layer() -> ConfigLayer {
552 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
555 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
553
556
554 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
557 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
555 layer.add(
558 layer.add(
556 section[..].into(),
559 section[..].into(),
557 item[..].into(),
560 item[..].into(),
558 value[..].into(),
561 value[..].into(),
559 None,
562 None,
560 );
563 );
561 };
564 };
562 // duplication of [tweakrc] from [ui.py]
565 // duplication of [tweakrc] from [ui.py]
563 add(b"ui", b"rollback", b"False");
566 add(b"ui", b"rollback", b"False");
564 add(b"ui", b"statuscopies", b"yes");
567 add(b"ui", b"statuscopies", b"yes");
565 add(b"ui", b"interface", b"curses");
568 add(b"ui", b"interface", b"curses");
566 add(b"ui", b"relative-paths", b"yes");
569 add(b"ui", b"relative-paths", b"yes");
567 add(b"commands", b"grep.all-files", b"True");
570 add(b"commands", b"grep.all-files", b"True");
568 add(b"commands", b"update.check", b"noconflict");
571 add(b"commands", b"update.check", b"noconflict");
569 add(b"commands", b"status.verbose", b"True");
572 add(b"commands", b"status.verbose", b"True");
570 add(b"commands", b"resolve.explicit-re-merge", b"True");
573 add(b"commands", b"resolve.explicit-re-merge", b"True");
571 add(b"git", b"git", b"1");
574 add(b"git", b"git", b"1");
572 add(b"git", b"showfunc", b"1");
575 add(b"git", b"showfunc", b"1");
573 add(b"git", b"word-diff", b"1");
576 add(b"git", b"word-diff", b"1");
574 layer
577 layer
575 }
578 }
576
579
577 // introduce the tweaked defaults as implied by ui.tweakdefaults
580 // introduce the tweaked defaults as implied by ui.tweakdefaults
578 pub fn tweakdefaults(&mut self) {
581 pub fn tweakdefaults(&mut self) {
579 self.layers.insert(0, Config::tweakdefaults_layer());
582 self.layers.insert(0, Config::tweakdefaults_layer());
580 }
583 }
581 }
584 }
582
585
583 #[cfg(test)]
586 #[cfg(test)]
584 mod tests {
587 mod tests {
585 use super::*;
588 use super::*;
586 use pretty_assertions::assert_eq;
589 use pretty_assertions::assert_eq;
587 use std::fs::File;
590 use std::fs::File;
588 use std::io::Write;
591 use std::io::Write;
589
592
590 #[test]
593 #[test]
591 fn test_include_layer_ordering() {
594 fn test_include_layer_ordering() {
592 let tmpdir = tempfile::tempdir().unwrap();
595 let tmpdir = tempfile::tempdir().unwrap();
593 let tmpdir_path = tmpdir.path();
596 let tmpdir_path = tmpdir.path();
594 let mut included_file =
597 let mut included_file =
595 File::create(&tmpdir_path.join("included.rc")).unwrap();
598 File::create(&tmpdir_path.join("included.rc")).unwrap();
596
599
597 included_file.write_all(b"[section]\nitem=value1").unwrap();
600 included_file.write_all(b"[section]\nitem=value1").unwrap();
598 let base_config_path = tmpdir_path.join("base.rc");
601 let base_config_path = tmpdir_path.join("base.rc");
599 let mut config_file = File::create(&base_config_path).unwrap();
602 let mut config_file = File::create(&base_config_path).unwrap();
600 let data =
603 let data =
601 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
604 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
602 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
605 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
603 config_file.write_all(data).unwrap();
606 config_file.write_all(data).unwrap();
604
607
605 let sources = vec![ConfigSource::AbsPath(base_config_path)];
608 let sources = vec![ConfigSource::AbsPath(base_config_path)];
606 let config = Config::load_from_explicit_sources(sources)
609 let config = Config::load_from_explicit_sources(sources)
607 .expect("expected valid config");
610 .expect("expected valid config");
608
611
609 let (_, value) = config.get_inner(b"section", b"item").unwrap();
612 let (_, value) = config.get_inner(b"section", b"item").unwrap();
610 assert_eq!(
613 assert_eq!(
611 value,
614 value,
612 &ConfigValue {
615 &ConfigValue {
613 bytes: b"value2".to_vec(),
616 bytes: b"value2".to_vec(),
614 line: Some(4)
617 line: Some(4)
615 }
618 }
616 );
619 );
617
620
618 let value = config.get(b"section", b"item").unwrap();
621 let value = config.get(b"section", b"item").unwrap();
619 assert_eq!(value, b"value2",);
622 assert_eq!(value, b"value2",);
620 assert_eq!(
623 assert_eq!(
621 config.get_all(b"section", b"item"),
624 config.get_all(b"section", b"item"),
622 [b"value2", b"value1", b"value0"]
625 [b"value2", b"value1", b"value0"]
623 );
626 );
624
627
625 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
628 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
626 assert_eq!(
629 assert_eq!(
627 config.get_byte_size(b"section2", b"size").unwrap(),
630 config.get_byte_size(b"section2", b"size").unwrap(),
628 Some(1024 + 512)
631 Some(1024 + 512)
629 );
632 );
630 assert!(config.get_u32(b"section2", b"not-count").is_err());
633 assert!(config.get_u32(b"section2", b"not-count").is_err());
631 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
634 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
632 }
635 }
633 }
636 }
General Comments 0
You need to be logged in to leave comments. Login now