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