The vendored code doesn't follow modern rustfmt's conventions.
This commit runs rustfmt so it does.
- skip-blame rustfmt
Alphare | |
durin42 |
hg-reviewers |
The vendored code doesn't follow modern rustfmt's conventions.
This commit runs rustfmt so it does.
No Linters Available |
No Unit Test Coverage |
We actually ran rustfmt, but it's an older version (rustfmt 1.3.0-stable (d334502 2019-06-09)) and I verified the code still formats under that particular version of rustfmt with empty rustfmt.toml. In other words, rustfmt itself seems to change over time.
Path | Packages | |||
---|---|---|---|---|
M | rust/hg-core/src/configparser/c_api.rs (21 lines) | |||
M | rust/hg-core/src/configparser/config.rs (357 lines) | |||
M | rust/hg-core/src/configparser/hg.rs (233 lines) | |||
M | rust/hg-core/src/configparser/lib.rs (4 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
e8ec811bf019 | 1eda551870ad | Gregory Szorc | Dec 7 2019, 10:01 PM |
/* | /* | ||||
* Copyright (c) Facebook, Inc. and its affiliates. | * Copyright (c) Facebook, Inc. and its affiliates. | ||||
* | * | ||||
* This software may be used and distributed according to the terms of the | * This software may be used and distributed according to the terms of the | ||||
* GNU General Public License version 2. | * GNU General Public License version 2. | ||||
*/ | */ | ||||
//! This module exports some symbols to allow calling the config parser from C/C++ | //! This module exports some symbols to allow calling the config parser from | ||||
//! C/C++ | |||||
use std::ffi::{CStr, OsStr}; | use std::ffi::{CStr, OsStr}; | ||||
use std::os::raw::c_char; | use std::os::raw::c_char; | ||||
use std::path::Path; | use std::path::Path; | ||||
use std::ptr; | use std::ptr; | ||||
use std::slice; | use std::slice; | ||||
use bytes::Bytes; | use bytes::Bytes; | ||||
} | } | ||||
/// Attempt to load and parse the config file at the specified path. | /// Attempt to load and parse the config file at the specified path. | ||||
/// If successful, returns a nullptr. | /// If successful, returns a nullptr. | ||||
/// Returns a Bytes object containing the error reason on failure; the | /// Returns a Bytes object containing the error reason on failure; the | ||||
/// error object is UTF-8 encoded text, and errors can span multiple lines. | /// error object is UTF-8 encoded text, and errors can span multiple lines. | ||||
#[cfg(unix)] | #[cfg(unix)] | ||||
#[no_mangle] | #[no_mangle] | ||||
pub extern "C" fn hgrc_configset_load_path(cfg: *mut ConfigSet, path: *const c_char) -> *mut Bytes { | pub extern "C" fn hgrc_configset_load_path( | ||||
cfg: *mut ConfigSet, | |||||
path: *const c_char, | |||||
) -> *mut Bytes { | |||||
debug_assert!(!path.is_null()); | debug_assert!(!path.is_null()); | ||||
debug_assert!(!cfg.is_null()); | debug_assert!(!cfg.is_null()); | ||||
use std::os::unix::ffi::OsStrExt; | use std::os::unix::ffi::OsStrExt; | ||||
let path_cstr = unsafe { CStr::from_ptr(path) }; | let path_cstr = unsafe { CStr::from_ptr(path) }; | ||||
let path_bytes = path_cstr.to_bytes(); | let path_bytes = path_cstr.to_bytes(); | ||||
let path = Path::new(OsStr::from_bytes(&path_bytes)); | let path = Path::new(OsStr::from_bytes(&path_bytes)); | ||||
let cfg = unsafe { &mut *cfg }; | let cfg = unsafe { &mut *cfg }; | ||||
load_path(cfg, path) | load_path(cfg, path) | ||||
} | } | ||||
/// Load system config files | /// Load system config files | ||||
#[no_mangle] | #[no_mangle] | ||||
pub extern "C" fn hgrc_configset_load_system(cfg: *mut ConfigSet) -> *mut Bytes { | pub extern "C" fn hgrc_configset_load_system( | ||||
cfg: *mut ConfigSet, | |||||
) -> *mut Bytes { | |||||
debug_assert!(!cfg.is_null()); | debug_assert!(!cfg.is_null()); | ||||
let cfg = unsafe { &mut *cfg }; | let cfg = unsafe { &mut *cfg }; | ||||
// Forces datapath to be the empty string as it doesn't | // Forces datapath to be the empty string as it doesn't | ||||
// appear to play a useful role in simply resolving config | // appear to play a useful role in simply resolving config | ||||
// settings for Eden. | // settings for Eden. | ||||
errors_to_bytes(cfg.load_system()) | errors_to_bytes(cfg.load_system()) | ||||
} | } | ||||
/// Load user config files | /// Load user config files | ||||
#[no_mangle] | #[no_mangle] | ||||
pub extern "C" fn hgrc_configset_load_user(cfg: *mut ConfigSet) -> *mut Bytes { | pub extern "C" fn hgrc_configset_load_user(cfg: *mut ConfigSet) -> *mut Bytes { | ||||
debug_assert!(!cfg.is_null()); | debug_assert!(!cfg.is_null()); | ||||
let cfg = unsafe { &mut *cfg }; | let cfg = unsafe { &mut *cfg }; | ||||
errors_to_bytes(cfg.load_user()) | errors_to_bytes(cfg.load_user()) | ||||
} | } | ||||
/// Returns a Bytes object holding the configuration value for the corresponding | /// Returns a Bytes object holding the configuration value for the | ||||
/// section name and key. If there is no matching section/key pair, returns nullptr. | /// corresponding section name and key. If there is no matching section/key | ||||
/// pair, returns nullptr. | |||||
#[no_mangle] | #[no_mangle] | ||||
pub extern "C" fn hgrc_configset_get( | pub extern "C" fn hgrc_configset_get( | ||||
cfg: *const ConfigSet, | cfg: *const ConfigSet, | ||||
section: *const u8, | section: *const u8, | ||||
section_len: usize, | section_len: usize, | ||||
name: *const u8, | name: *const u8, | ||||
name_len: usize, | name_len: usize, | ||||
) -> *mut Bytes { | ) -> *mut Bytes { | ||||
} | } | ||||
#[repr(C)] | #[repr(C)] | ||||
pub struct ByteData { | pub struct ByteData { | ||||
ptr: *const u8, | ptr: *const u8, | ||||
len: usize, | len: usize, | ||||
} | } | ||||
/// Returns the data pointer and length for a Bytes object, suitable for constructing | /// Returns the data pointer and length for a Bytes object, suitable for | ||||
/// a folly::ByteRange. | /// constructing a folly::ByteRange. | ||||
#[no_mangle] | #[no_mangle] | ||||
pub extern "C" fn hgrc_bytes_data(bytes: *const Bytes) -> ByteData { | pub extern "C" fn hgrc_bytes_data(bytes: *const Bytes) -> ByteData { | ||||
debug_assert!(!bytes.is_null()); | debug_assert!(!bytes.is_null()); | ||||
let bytes = unsafe { &*bytes }; | let bytes = unsafe { &*bytes }; | ||||
ByteData { | ByteData { | ||||
ptr: bytes.as_ptr(), | ptr: bytes.as_ptr(), | ||||
len: bytes.len(), | len: bytes.len(), | ||||
} | } |
struct Section { | struct Section { | ||||
items: IndexMap<Bytes, Vec<ValueSource>>, | items: IndexMap<Bytes, Vec<ValueSource>>, | ||||
} | } | ||||
/// A config value with associated metadata like where it comes from. | /// A config value with associated metadata like where it comes from. | ||||
#[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||
pub struct ValueSource { | pub struct ValueSource { | ||||
value: Option<Bytes>, | value: Option<Bytes>, | ||||
source: Bytes, // global, user, repo, "--config", or an extension name, etc. | source: Bytes, /* global, user, repo, "--config", or an extension name, | ||||
* etc. */ | |||||
location: Option<ValueLocation>, | location: Option<ValueLocation>, | ||||
} | } | ||||
/// The on-disk file name and byte offsets that provide the config value. | /// The on-disk file name and byte offsets that provide the config value. | ||||
/// Useful if applications want to edit config values in-place. | /// Useful if applications want to edit config values in-place. | ||||
#[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||
struct ValueLocation { | struct ValueLocation { | ||||
path: Arc<PathBuf>, | path: Arc<PathBuf>, | ||||
content: Bytes, | content: Bytes, | ||||
location: Range<usize>, | location: Range<usize>, | ||||
} | } | ||||
/// Options that affects config setting functions like `load_path`, `parse`, | /// Options that affects config setting functions like `load_path`, `parse`, | ||||
/// and `set`. | /// and `set`. | ||||
#[derive(Default)] | #[derive(Default)] | ||||
pub struct Options { | pub struct Options { | ||||
source: Bytes, | source: Bytes, | ||||
filters: Vec<Box<dyn Fn(Bytes, Bytes, Option<Bytes>) -> Option<(Bytes, Bytes, Option<Bytes>)>>>, | filters: Vec< | ||||
Box< | |||||
dyn Fn( | |||||
Bytes, | |||||
Bytes, | |||||
Option<Bytes>, | |||||
) -> Option<(Bytes, Bytes, Option<Bytes>)>, | |||||
>, | |||||
>, | |||||
} | } | ||||
impl ConfigSet { | impl ConfigSet { | ||||
/// Return an empty `ConfigSet`. | /// Return an empty `ConfigSet`. | ||||
pub fn new() -> Self { | pub fn new() -> Self { | ||||
Default::default() | Default::default() | ||||
} | } | ||||
/// Load config files at given path. The path is a file. | /// Load config files at given path. The path is a file. | ||||
/// | /// | ||||
/// If `path` is a directory, it is ignored. | /// If `path` is a directory, it is ignored. | ||||
/// If `path` is a file, it will be loaded directly. | /// If `path` is a file, it will be loaded directly. | ||||
/// | /// | ||||
/// A config file can use `%include` to load other paths (directories or files). They will | /// A config file can use `%include` to load other paths (directories or | ||||
/// be loaded recursively. Includes take effect in place, instead of deferred. For example, | /// files). They will be loaded recursively. Includes take effect in | ||||
/// with the following two files: | /// place, instead of deferred. For example, with the following two | ||||
/// files: | |||||
/// | /// | ||||
/// ```plain,ignore | /// ```plain,ignore | ||||
/// # This is 1.rc | /// # This is 1.rc | ||||
/// [section] | /// [section] | ||||
/// x = 1 | /// x = 1 | ||||
/// %include 2.rc | /// %include 2.rc | ||||
/// y = 2 | /// y = 2 | ||||
/// | /// | ||||
/// # This is 2.rc | /// # This is 2.rc | ||||
/// [section] | /// [section] | ||||
/// x = 3 | /// x = 3 | ||||
/// y = 4 | /// y = 4 | ||||
/// ``` | /// ``` | ||||
/// | /// | ||||
/// After loading `1.rc`. `x` is set to 3 and `y` is set to 2. | /// After loading `1.rc`. `x` is set to 3 and `y` is set to 2. | ||||
/// | /// | ||||
/// Loading a file that is already parsed or being parsed by this `load_path` call is ignored, | /// Loading a file that is already parsed or being parsed by this | ||||
/// to avoid infinite loop. A separate `load_path` call would not ignore files loaded by | /// `load_path` call is ignored, to avoid infinite loop. A separate | ||||
/// other `load_path` calls. | /// `load_path` call would not ignore files loaded by other `load_path` | ||||
/// | /// calls. | ||||
/// Return a list of errors. An error pasing a file will stop that file from loading, without | /// | ||||
/// affecting other files. | /// Return a list of errors. An error pasing a file will stop that file | ||||
pub fn load_path<P: AsRef<Path>>(&mut self, path: P, opts: &Options) -> Vec<Error> { | /// from loading, without affecting other files. | ||||
pub fn load_path<P: AsRef<Path>>( | |||||
&mut self, | |||||
path: P, | |||||
opts: &Options, | |||||
) -> Vec<Error> { | |||||
let mut visited = HashSet::new(); | let mut visited = HashSet::new(); | ||||
let mut errors = Vec::new(); | let mut errors = Vec::new(); | ||||
self.load_file(path.as_ref(), opts, &mut visited, &mut errors); | self.load_file(path.as_ref(), opts, &mut visited, &mut errors); | ||||
errors | errors | ||||
} | } | ||||
/// Load content of an unnamed config file. The `ValueLocation`s of loaded config items will | /// Load content of an unnamed config file. The `ValueLocation`s of loaded | ||||
/// have an empty `path`. | /// config items will have an empty `path`. | ||||
/// | /// | ||||
/// Return a list of errors. | /// Return a list of errors. | ||||
pub fn parse<B: Into<Bytes>>(&mut self, content: B, opts: &Options) -> Vec<Error> { | pub fn parse<B: Into<Bytes>>( | ||||
&mut self, | |||||
content: B, | |||||
opts: &Options, | |||||
) -> Vec<Error> { | |||||
let mut visited = HashSet::new(); | let mut visited = HashSet::new(); | ||||
let mut errors = Vec::new(); | let mut errors = Vec::new(); | ||||
let buf = content.into(); | let buf = content.into(); | ||||
self.load_file_content(Path::new(""), buf, opts, &mut visited, &mut errors); | self.load_file_content( | ||||
Path::new(""), | |||||
buf, | |||||
opts, | |||||
&mut visited, | |||||
&mut errors, | |||||
); | |||||
errors | errors | ||||
} | } | ||||
/// Get config sections. | /// Get config sections. | ||||
pub fn sections(&self) -> Vec<Bytes> { | pub fn sections(&self) -> Vec<Bytes> { | ||||
self.sections.keys().cloned().collect() | self.sections.keys().cloned().collect() | ||||
} | } | ||||
/// Get config names in the given section. Sorted by insertion order. | /// Get config names in the given section. Sorted by insertion order. | ||||
pub fn keys<S: Into<Bytes>>(&self, section: S) -> Vec<Bytes> { | pub fn keys<S: Into<Bytes>>(&self, section: S) -> Vec<Bytes> { | ||||
self.sections | self.sections | ||||
.get(§ion.into()) | .get(§ion.into()) | ||||
.map(|section| section.items.keys().cloned().collect()) | .map(|section| section.items.keys().cloned().collect()) | ||||
.unwrap_or(Vec::new()) | .unwrap_or(Vec::new()) | ||||
} | } | ||||
/// Get config value for a given config. | /// Get config value for a given config. | ||||
/// Return `None` if the config item does not exist or is unset. | /// Return `None` if the config item does not exist or is unset. | ||||
pub fn get<S: Into<Bytes>, N: Into<Bytes>>(&self, section: S, name: N) -> Option<Bytes> { | pub fn get<S: Into<Bytes>, N: Into<Bytes>>( | ||||
&self, | |||||
section: S, | |||||
name: N, | |||||
) -> Option<Bytes> { | |||||
self.sections.get(§ion.into()).and_then(|section| { | self.sections.get(§ion.into()).and_then(|section| { | ||||
section | section.items.get(&name.into()).and_then(|values| { | ||||
.items | values.last().and_then(|value| value.value.clone()) | ||||
.get(&name.into()) | }) | ||||
.and_then(|values| values.last().and_then(|value| value.value.clone())) | |||||
}) | }) | ||||
} | } | ||||
/// Get detailed sources of a given config, including overrides, and source information. | /// Get detailed sources of a given config, including overrides, and source | ||||
/// The last item in the returned vector is the latest value that is considered effective. | /// information. The last item in the returned vector is the latest | ||||
/// value that is considered effective. | |||||
/// | /// | ||||
/// Return an emtpy vector if the config does not exist. | /// Return an emtpy vector if the config does not exist. | ||||
pub fn get_sources<S: Into<Bytes>, N: Into<Bytes>>( | pub fn get_sources<S: Into<Bytes>, N: Into<Bytes>>( | ||||
&self, | &self, | ||||
section: S, | section: S, | ||||
name: N, | name: N, | ||||
) -> Vec<ValueSource> { | ) -> Vec<ValueSource> { | ||||
self.sections | self.sections | ||||
.get(§ion.into()) | .get(§ion.into()) | ||||
.and_then(|section| section.items.get(&name.into()).map(|values| values.clone())) | .and_then(|section| { | ||||
section.items.get(&name.into()).map(|values| values.clone()) | |||||
}) | |||||
.unwrap_or(Vec::new()) | .unwrap_or(Vec::new()) | ||||
} | } | ||||
/// Set a config item directly. `section`, `name` locates the config. `value` is the new value. | /// Set a config item directly. `section`, `name` locates the config. | ||||
/// `source` is some annotation about who set it, ex. "reporc", "userrc", "--config", etc. | /// `value` is the new value. `source` is some annotation about who set | ||||
/// it, ex. "reporc", "userrc", "--config", etc. | |||||
pub fn set<T: Into<Bytes>, N: Into<Bytes>>( | pub fn set<T: Into<Bytes>, N: Into<Bytes>>( | ||||
&mut self, | &mut self, | ||||
section: T, | section: T, | ||||
name: N, | name: N, | ||||
value: Option<&[u8]>, | value: Option<&[u8]>, | ||||
opts: &Options, | opts: &Options, | ||||
) { | ) { | ||||
let section = section.into(); | let section = section.into(); | ||||
let name = name.into(); | let name = name.into(); | ||||
let value = value.map(|v| Bytes::from(v)); | let value = value.map(|v| Bytes::from(v)); | ||||
self.set_internal(section, name, value, None, &opts) | self.set_internal(section, name, value, None, &opts) | ||||
} | } | ||||
fn set_internal( | fn set_internal( | ||||
&mut self, | &mut self, | ||||
section: Bytes, | section: Bytes, | ||||
name: Bytes, | name: Bytes, | ||||
value: Option<Bytes>, | value: Option<Bytes>, | ||||
location: Option<ValueLocation>, | location: Option<ValueLocation>, | ||||
opts: &Options, | opts: &Options, | ||||
) { | ) { | ||||
let filtered = opts | let filtered = opts.filters.iter().fold( | ||||
.filters | Some((section, name, value)), | ||||
.iter() | move |acc, func| { | ||||
.fold(Some((section, name, value)), move |acc, func| { | acc.and_then(|(section, name, value)| { | ||||
acc.and_then(|(section, name, value)| func(section, name, value)) | func(section, name, value) | ||||
}); | }) | ||||
}, | |||||
); | |||||
if let Some((section, name, value)) = filtered { | if let Some((section, name, value)) = filtered { | ||||
self.sections | self.sections | ||||
.entry(section) | .entry(section) | ||||
.or_insert_with(|| Default::default()) | .or_insert_with(|| Default::default()) | ||||
.items | .items | ||||
.entry(name) | .entry(name) | ||||
.or_insert_with(|| Vec::with_capacity(1)) | .or_insert_with(|| Vec::with_capacity(1)) | ||||
.push(ValueSource { | .push(ValueSource { | ||||
errors.push(Error::Io(path.to_path_buf(), error)); | errors.push(Error::Io(path.to_path_buf(), error)); | ||||
return; | return; | ||||
} | } | ||||
buf.push(b'\n'); | buf.push(b'\n'); | ||||
let buf = Bytes::from(buf); | let buf = Bytes::from(buf); | ||||
self.load_file_content(path, buf, opts, visited, errors); | self.load_file_content(path, buf, opts, visited, errors); | ||||
} | } | ||||
Err(error) => errors.push(Error::Io(path.to_path_buf(), error)), | Err(error) => { | ||||
errors.push(Error::Io(path.to_path_buf(), error)) | |||||
} | |||||
} | } | ||||
} else { | } else { | ||||
// On Windows, a UNC path `\\?\C:\foo\.\x` will fail to canonicalize | // On Windows, a UNC path `\\?\C:\foo\.\x` will fail to | ||||
// because it contains `.`. That path can be constructed by using | // canonicalize because it contains `.`. That path can | ||||
// `PathBuf::join` to concatenate a UNC path `\\?\C:\foo` with | // be constructed by using `PathBuf::join` to | ||||
// a "normal" path `.\x`. | // concatenate a UNC path `\\?\C:\foo` with a "normal" | ||||
// Try to fix it automatically by stripping the UNC prefix and retry | // path `.\x`. Try to fix it automatically by stripping | ||||
// `canonicalize`. `C:\foo\.\x` would be canonicalized without errors. | // the UNC prefix and retry `canonicalize`. | ||||
// `C:\foo\.\x` would be canonicalized without errors. | |||||
#[cfg(windows)] | #[cfg(windows)] | ||||
{ | { | ||||
if let Some(path_str) = path.to_str() { | if let Some(path_str) = path.to_str() { | ||||
if path_str.starts_with(r"\\?\") { | if path_str.starts_with(r"\\?\") { | ||||
let path = Path::new(&path_str[4..]); | let path = Path::new(&path_str[4..]); | ||||
self.load_file(&path, opts, visited, errors); | self.load_file(&path, opts, visited, errors); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// If `path.canonicalize` reports an error. It's usually the path cannot | // If `path.canonicalize` reports an error. It's usually the path | ||||
// be resolved (ex. does not exist). It is considered normal and is not | // cannot be resolved (ex. does not exist). It is considered | ||||
// reported in `errors`. | // normal and is not reported in `errors`. | ||||
} | } | ||||
fn load_file_content( | fn load_file_content( | ||||
&mut self, | &mut self, | ||||
path: &Path, | path: &Path, | ||||
buf: Bytes, | buf: Bytes, | ||||
opts: &Options, | opts: &Options, | ||||
visited: &mut HashSet<PathBuf>, | visited: &mut HashSet<PathBuf>, | ||||
errors: &mut Vec<Error>, | errors: &mut Vec<Error>, | ||||
) { | ) { | ||||
let mut section = Bytes::new(); | let mut section = Bytes::new(); | ||||
let shared_path = Arc::new(path.to_path_buf()); // use Arc to do shallow copy | let shared_path = Arc::new(path.to_path_buf()); // use Arc to do shallow copy | ||||
let skip_include = path.parent().is_none(); // skip handling %include if path is empty | let skip_include = path.parent().is_none(); // skip handling %include if path is empty | ||||
// Utilities to avoid too much indentation. | // Utilities to avoid too much indentation. | ||||
let handle_value = |this: &mut ConfigSet, | let handle_value = | ||||
|this: &mut ConfigSet, | |||||
pair: Pair, | pair: Pair, | ||||
section: Bytes, | section: Bytes, | ||||
name: Bytes, | name: Bytes, | ||||
location: ValueLocation| { | location: ValueLocation| { | ||||
let pairs = pair.into_inner(); | let pairs = pair.into_inner(); | ||||
let mut lines = Vec::with_capacity(1); | let mut lines = Vec::with_capacity(1); | ||||
for pair in pairs { | for pair in pairs { | ||||
if Rule::line == pair.as_rule() { | if Rule::line == pair.as_rule() { | ||||
lines.push(extract(&buf, pair.as_span())); | lines.push(extract(&buf, pair.as_span())); | ||||
} | } | ||||
} | } | ||||
let value = match lines.len() { | let value = match lines.len() { | ||||
1 => lines[0].clone(), | 1 => lines[0].clone(), | ||||
_ => Bytes::from(lines.join(&b'\n')), | _ => Bytes::from(lines.join(&b'\n')), | ||||
}; | }; | ||||
let (start, end) = strip_offsets(&value, 0, value.len()); | let (start, end) = strip_offsets(&value, 0, value.len()); | ||||
let value = value.slice(start, end); | let value = value.slice(start, end); | ||||
this.set_internal(section, name, value.into(), location.into(), opts) | this.set_internal( | ||||
section, | |||||
name, | |||||
value.into(), | |||||
location.into(), | |||||
opts, | |||||
) | |||||
}; | }; | ||||
let handle_config_item = |this: &mut ConfigSet, pair: Pair, section: Bytes| { | let handle_config_item = | ||||
|this: &mut ConfigSet, pair: Pair, section: Bytes| { | |||||
let pairs = pair.into_inner(); | let pairs = pair.into_inner(); | ||||
let mut name = Bytes::new(); | let mut name = Bytes::new(); | ||||
for pair in pairs { | for pair in pairs { | ||||
match pair.as_rule() { | match pair.as_rule() { | ||||
Rule::config_name => name = extract(&buf, pair.as_span()), | Rule::config_name => { | ||||
name = extract(&buf, pair.as_span()) | |||||
} | |||||
Rule::value => { | Rule::value => { | ||||
let span = pair.as_span(); | let span = pair.as_span(); | ||||
let location = ValueLocation { | let location = ValueLocation { | ||||
path: shared_path.clone(), | path: shared_path.clone(), | ||||
content: buf.clone(), | content: buf.clone(), | ||||
location: span.start()..span.end(), | location: span.start()..span.end(), | ||||
}; | }; | ||||
return handle_value(this, pair, section, name, location); | return handle_value( | ||||
this, pair, section, name, location, | |||||
); | |||||
} | } | ||||
_ => (), | _ => (), | ||||
} | } | ||||
} | } | ||||
unreachable!(); | unreachable!(); | ||||
}; | }; | ||||
let handle_section = |pair: Pair, section: &mut Bytes| { | let handle_section = |pair: Pair, section: &mut Bytes| { | ||||
let pairs = pair.into_inner(); | let pairs = pair.into_inner(); | ||||
for pair in pairs { | for pair in pairs { | ||||
match pair.as_rule() { | match pair.as_rule() { | ||||
Rule::section_name => { | Rule::section_name => { | ||||
*section = extract(&buf, pair.as_span()); | *section = extract(&buf, pair.as_span()); | ||||
return; | return; | ||||
} | } | ||||
_ => (), | _ => (), | ||||
} | } | ||||
} | } | ||||
unreachable!(); | unreachable!(); | ||||
}; | }; | ||||
let mut handle_include = |this: &mut ConfigSet, pair: Pair, errors: &mut Vec<Error>| { | let mut handle_include = | ||||
|this: &mut ConfigSet, pair: Pair, errors: &mut Vec<Error>| { | |||||
let pairs = pair.into_inner(); | let pairs = pair.into_inner(); | ||||
for pair in pairs { | for pair in pairs { | ||||
match pair.as_rule() { | match pair.as_rule() { | ||||
Rule::line => { | Rule::line => { | ||||
if !skip_include { | if !skip_include { | ||||
let include_path = pair.as_str(); | let include_path = pair.as_str(); | ||||
let full_include_path = | let full_include_path = path | ||||
path.parent().unwrap().join(expand_path(include_path)); | .parent() | ||||
this.load_file(&full_include_path, opts, visited, errors); | .unwrap() | ||||
.join(expand_path(include_path)); | |||||
this.load_file( | |||||
&full_include_path, | |||||
opts, | |||||
visited, | |||||
errors, | |||||
); | |||||
} | } | ||||
} | } | ||||
_ => (), | _ => (), | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
let handle_unset = |this: &mut ConfigSet, pair: Pair, section: &Bytes| { | let handle_unset = | ||||
|this: &mut ConfigSet, pair: Pair, section: &Bytes| { | |||||
let unset_span = pair.as_span(); | let unset_span = pair.as_span(); | ||||
let pairs = pair.into_inner(); | let pairs = pair.into_inner(); | ||||
for pair in pairs { | for pair in pairs { | ||||
match pair.as_rule() { | match pair.as_rule() { | ||||
Rule::config_name => { | Rule::config_name => { | ||||
let name = extract(&buf, pair.as_span()); | let name = extract(&buf, pair.as_span()); | ||||
let location = ValueLocation { | let location = ValueLocation { | ||||
path: shared_path.clone(), | path: shared_path.clone(), | ||||
content: buf.clone(), | content: buf.clone(), | ||||
location: unset_span.start()..unset_span.end(), | location: unset_span.start()..unset_span.end(), | ||||
}; | }; | ||||
return this.set_internal( | return this.set_internal( | ||||
section.clone(), | section.clone(), | ||||
name, | name, | ||||
None, | None, | ||||
location.into(), | location.into(), | ||||
opts, | opts, | ||||
); | ); | ||||
} | } | ||||
_ => (), | _ => (), | ||||
} | } | ||||
} | } | ||||
unreachable!(); | unreachable!(); | ||||
}; | }; | ||||
let mut handle_directive = | let mut handle_directive = | ||||
|this: &mut ConfigSet, pair: Pair, section: &Bytes, errors: &mut Vec<Error>| { | |this: &mut ConfigSet, | ||||
pair: Pair, | |||||
section: &Bytes, | |||||
errors: &mut Vec<Error>| { | |||||
let pairs = pair.into_inner(); | let pairs = pair.into_inner(); | ||||
for pair in pairs { | for pair in pairs { | ||||
match pair.as_rule() { | match pair.as_rule() { | ||||
Rule::include => handle_include(this, pair, errors), | Rule::include => handle_include(this, pair, errors), | ||||
Rule::unset => handle_unset(this, pair, section), | Rule::unset => handle_unset(this, pair, section), | ||||
_ => (), | _ => (), | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
let text = match str::from_utf8(&buf) { | let text = match str::from_utf8(&buf) { | ||||
Ok(text) => text, | Ok(text) => text, | ||||
Err(error) => return errors.push(Error::Utf8(path.to_path_buf(), error)), | Err(error) => { | ||||
return errors.push(Error::Utf8(path.to_path_buf(), error)) | |||||
} | |||||
}; | }; | ||||
let pairs = match ConfigParser::parse(Rule::file, &text) { | let pairs = match ConfigParser::parse(Rule::file, &text) { | ||||
Ok(pairs) => pairs, | Ok(pairs) => pairs, | ||||
Err(error) => { | Err(error) => { | ||||
return errors.push(Error::Parse(path.to_path_buf(), format!("{}", error))); | return errors.push(Error::Parse( | ||||
path.to_path_buf(), | |||||
format!("{}", error), | |||||
)); | |||||
} | } | ||||
}; | }; | ||||
for pair in pairs { | for pair in pairs { | ||||
match pair.as_rule() { | match pair.as_rule() { | ||||
Rule::config_item => handle_config_item(self, pair, section.clone()), | Rule::config_item => { | ||||
handle_config_item(self, pair, section.clone()) | |||||
} | |||||
Rule::section => handle_section(pair, &mut section), | Rule::section => handle_section(pair, &mut section), | ||||
Rule::directive => handle_directive(self, pair, §ion, errors), | Rule::directive => { | ||||
Rule::blank_line | Rule::comment_line | Rule::new_line | Rule::EOI => (), | handle_directive(self, pair, §ion, errors) | ||||
} | |||||
Rule::blank_line | |||||
| Rule::comment_line | |||||
| Rule::new_line | |||||
| Rule::EOI => (), | |||||
Rule::comment_start | Rule::comment_start | ||||
| Rule::compound | | Rule::compound | ||||
| Rule::config_name | | Rule::config_name | ||||
| Rule::equal_sign | | Rule::equal_sign | ||||
| Rule::file | | Rule::file | ||||
| Rule::include | | Rule::include | ||||
| Rule::left_bracket | | Rule::left_bracket | ||||
} | } | ||||
impl ValueSource { | impl ValueSource { | ||||
/// Return the actual value stored in this config value, or `None` if uset. | /// Return the actual value stored in this config value, or `None` if uset. | ||||
pub fn value(&self) -> &Option<Bytes> { | pub fn value(&self) -> &Option<Bytes> { | ||||
&self.value | &self.value | ||||
} | } | ||||
/// Return the "source" information for the config value. It's usually who sets the config, | /// Return the "source" information for the config value. It's usually who | ||||
/// like "--config", "user_hgrc", "system_hgrc", etc. | /// sets the config, like "--config", "user_hgrc", "system_hgrc", etc. | ||||
pub fn source(&self) -> &Bytes { | pub fn source(&self) -> &Bytes { | ||||
&self.source | &self.source | ||||
} | } | ||||
/// Return the file path and byte range for the exact config value, | /// Return the file path and byte range for the exact config value, | ||||
/// or `None` if there is no such information. | /// or `None` if there is no such information. | ||||
/// | /// | ||||
/// If the value is `None`, the byte range is for the "%unset" statement. | /// If the value is `None`, the byte range is for the "%unset" statement. | ||||
pub fn location(&self) -> Option<(PathBuf, Range<usize>)> { | pub fn location(&self) -> Option<(PathBuf, Range<usize>)> { | ||||
match self.location { | match self.location { | ||||
Some(ref src) => Some((src.path.as_ref().to_path_buf(), src.location.clone())), | Some(ref src) => { | ||||
Some((src.path.as_ref().to_path_buf(), src.location.clone())) | |||||
} | |||||
None => None, | None => None, | ||||
} | } | ||||
} | } | ||||
/// Return the file content. Or `None` if there is no such information. | /// Return the file content. Or `None` if there is no such information. | ||||
pub fn file_content(&self) -> Option<Bytes> { | pub fn file_content(&self) -> Option<Bytes> { | ||||
match self.location { | match self.location { | ||||
Some(ref src) => Some(src.content.clone()), | Some(ref src) => Some(src.content.clone()), | ||||
None => None, | None => None, | ||||
} | } | ||||
} | } | ||||
} | } | ||||
impl Options { | impl Options { | ||||
/// Create a default `Options`. | /// Create a default `Options`. | ||||
pub fn new() -> Self { | pub fn new() -> Self { | ||||
Self::default() | Self::default() | ||||
} | } | ||||
/// Append a filter. A filter can decide to ignore a config item, or change its section, | /// Append a filter. A filter can decide to ignore a config item, or change | ||||
/// config name, or even value. The filter function takes a tuple of `(section, name, value)` | /// its section, config name, or even value. The filter function takes | ||||
/// and outputs `None` to prevent inserting that value, or `Some((section, name, value))` to | /// a tuple of `(section, name, value)` and outputs `None` to prevent | ||||
/// inserting that value, or `Some((section, name, value))` to | |||||
/// insert it with optionally different name or values. | /// insert it with optionally different name or values. | ||||
/// | /// | ||||
/// Filters inserted first will be executed first. | /// Filters inserted first will be executed first. | ||||
pub fn append_filter( | pub fn append_filter( | ||||
mut self, | mut self, | ||||
filter: Box<dyn Fn(Bytes, Bytes, Option<Bytes>) -> Option<(Bytes, Bytes, Option<Bytes>)>>, | filter: Box< | ||||
dyn Fn( | |||||
Bytes, | |||||
Bytes, | |||||
Option<Bytes>, | |||||
) -> Option<(Bytes, Bytes, Option<Bytes>)>, | |||||
>, | |||||
) -> Self { | ) -> Self { | ||||
self.filters.push(filter); | self.filters.push(filter); | ||||
self | self | ||||
} | } | ||||
/// Set `source` information. It is about who initialized the config loading. For example, | /// Set `source` information. It is about who initialized the config | ||||
/// "user_hgrc" indicates it is from the user config file, "--config" indicates it is from the | /// loading. For example, "user_hgrc" indicates it is from the user | ||||
/// global "--config" command line flag, "env" indicates it is translated from an environment | /// config file, "--config" indicates it is from the global "--config" | ||||
/// command line flag, "env" indicates it is translated from an environment | |||||
/// variable (ex. "PAGER"), etc. | /// variable (ex. "PAGER"), etc. | ||||
pub fn source<B: Into<Bytes>>(mut self, source: B) -> Self { | pub fn source<B: Into<Bytes>>(mut self, source: B) -> Self { | ||||
self.source = source.into(); | self.source = source.into(); | ||||
self | self | ||||
} | } | ||||
} | } | ||||
/// Convert a "source" string to an `Options`. | /// Convert a "source" string to an `Options`. | ||||
impl<S: Into<Bytes>> From<S> for Options { | impl<S: Into<Bytes>> From<S> for Options { | ||||
fn from(source: S) -> Options { | fn from(source: S) -> Options { | ||||
Options::new().source(source.into()) | Options::new().source(source.into()) | ||||
} | } | ||||
} | } | ||||
/// Remove space characters from both ends. Remove newline characters from the end. | /// Remove space characters from both ends. Remove newline characters from the | ||||
/// `start` position is inclusive, `end` is exclusive. | /// end. `start` position is inclusive, `end` is exclusive. | ||||
/// Return the stripped `start` and `end` offsets. | /// Return the stripped `start` and `end` offsets. | ||||
#[inline] | #[inline] | ||||
fn strip_offsets(buf: &Bytes, start: usize, end: usize) -> (usize, usize) { | fn strip_offsets(buf: &Bytes, start: usize, end: usize) -> (usize, usize) { | ||||
let mut start = start; | let mut start = start; | ||||
let mut end = end; | let mut end = end; | ||||
while start < end && b" \t".contains(&buf[start]) { | while start < end && b" \t".contains(&buf[start]) { | ||||
start += 1 | start += 1 | ||||
} | } | ||||
// Will be loaded. Shouldn't cause cycles. | // Will be loaded. Shouldn't cause cycles. | ||||
write_file(dir.path().join("e.rc"), "[x]\ne=e\n%include f.rc"); | write_file(dir.path().join("e.rc"), "[x]\ne=e\n%include f.rc"); | ||||
write_file( | write_file( | ||||
dir.path().join("f.rc"), | dir.path().join("f.rc"), | ||||
"[x]\nf=f\n%include e.rc\n%include rootrc", | "[x]\nf=f\n%include e.rc\n%include rootrc", | ||||
); | ); | ||||
let mut cfg = ConfigSet::new(); | let mut cfg = ConfigSet::new(); | ||||
let errors = cfg.load_path(dir.path().join("rootrc"), &"test_parse_include".into()); | let errors = cfg.load_path( | ||||
dir.path().join("rootrc"), | |||||
&"test_parse_include".into(), | |||||
); | |||||
assert!(errors.is_empty()); | assert!(errors.is_empty()); | ||||
assert_eq!(cfg.sections(), vec![Bytes::from("x"), Bytes::from("y")]); | assert_eq!(cfg.sections(), vec![Bytes::from("x"), Bytes::from("y")]); | ||||
assert_eq!( | assert_eq!( | ||||
cfg.keys("x"), | cfg.keys("x"), | ||||
vec![ | vec![ | ||||
Bytes::from("b"), | Bytes::from("b"), | ||||
Bytes::from("a"), | Bytes::from("a"), | ||||
"%include ./${FOO}1/$FOO/3.rc\n\ | "%include ./${FOO}1/$FOO/3.rc\n\ | ||||
%include ./%FOO%2/%FOO%/4.rc\n", | %include ./%FOO%2/%FOO%/4.rc\n", | ||||
); | ); | ||||
write_file(dir.path().join("f1/f/3.rc"), "[x]\na=1\n"); | write_file(dir.path().join("f1/f/3.rc"), "[x]\na=1\n"); | ||||
write_file(dir.path().join("f2/f/4.rc"), "[y]\nb=2\n"); | write_file(dir.path().join("f2/f/4.rc"), "[y]\nb=2\n"); | ||||
let mut cfg = ConfigSet::new(); | let mut cfg = ConfigSet::new(); | ||||
let errors = cfg.load_path(dir.path().join("rootrc"), &"include_expand".into()); | let errors = | ||||
cfg.load_path(dir.path().join("rootrc"), &"include_expand".into()); | |||||
assert!(errors.is_empty()); | assert!(errors.is_empty()); | ||||
assert_eq!(cfg.get("x", "a"), Some(Bytes::from("1"))); | assert_eq!(cfg.get("x", "a"), Some(Bytes::from("1"))); | ||||
assert_eq!(cfg.get("y", "b"), Some(Bytes::from("2"))); | assert_eq!(cfg.get("y", "b"), Some(Bytes::from("2"))); | ||||
} | } | ||||
} | } |
pub const HGPLAIN: &str = "HGPLAIN"; | pub const HGPLAIN: &str = "HGPLAIN"; | ||||
pub const HGPLAINEXCEPT: &str = "HGPLAINEXCEPT"; | pub const HGPLAINEXCEPT: &str = "HGPLAINEXCEPT"; | ||||
pub const HGRCPATH: &str = "HGRCPATH"; | pub const HGRCPATH: &str = "HGRCPATH"; | ||||
pub trait OptionsHgExt { | pub trait OptionsHgExt { | ||||
/// Drop configs according to `$HGPLAIN` and `$HGPLAINEXCEPT`. | /// Drop configs according to `$HGPLAIN` and `$HGPLAINEXCEPT`. | ||||
fn process_hgplain(self) -> Self; | fn process_hgplain(self) -> Self; | ||||
/// Set read-only config items. `items` contains a list of tuple `(section, name)`. | /// Set read-only config items. `items` contains a list of tuple `(section, | ||||
/// Setting those items to new value will be ignored. | /// name)`. Setting those items to new value will be ignored. | ||||
fn readonly_items<S: Into<Bytes>, N: Into<Bytes>>(self, items: Vec<(S, N)>) -> Self; | fn readonly_items<S: Into<Bytes>, N: Into<Bytes>>( | ||||
self, | |||||
/// Set section remap. If a section name matches an entry key, it will be treated as if the | items: Vec<(S, N)>, | ||||
/// name is the entry value. The remap wouldn't happen recursively. For example, with a | ) -> Self; | ||||
/// `{"A": "B", "B": "C"}` map, section name "A" will be treated as "B", not "C". | |||||
/// Set section remap. If a section name matches an entry key, it will be | |||||
/// treated as if the name is the entry value. The remap wouldn't | |||||
/// happen recursively. For example, with a `{"A": "B", "B": "C"}` map, | |||||
/// section name "A" will be treated as "B", not "C". | |||||
/// This is implemented via `append_filter`. | /// This is implemented via `append_filter`. | ||||
fn remap_sections<K: Eq + Hash + Into<Bytes>, V: Into<Bytes>>( | fn remap_sections<K: Eq + Hash + Into<Bytes>, V: Into<Bytes>>( | ||||
self, | self, | ||||
remap: HashMap<K, V>, | remap: HashMap<K, V>, | ||||
) -> Self; | ) -> Self; | ||||
/// Set section whitelist. Sections outside the whitelist won't be loaded. | /// Set section whitelist. Sections outside the whitelist won't be loaded. | ||||
/// This is implemented via `append_filter`. | /// This is implemented via `append_filter`. | ||||
fn whitelist_sections<B: Clone + Into<Bytes>>(self, sections: Vec<B>) -> Self; | fn whitelist_sections<B: Clone + Into<Bytes>>( | ||||
self, | |||||
sections: Vec<B>, | |||||
) -> Self; | |||||
} | } | ||||
pub trait ConfigSetHgExt { | pub trait ConfigSetHgExt { | ||||
/// Load system config files if `$HGRCPATH` is not set. | /// Load system config files if `$HGRCPATH` is not set. | ||||
/// Return errors parsing files. | /// Return errors parsing files. | ||||
fn load_system(&mut self) -> Vec<Error>; | fn load_system(&mut self) -> Vec<Error>; | ||||
/// Load user config files (and environment variables). If `$HGRCPATH` is | /// Load user config files (and environment variables). If `$HGRCPATH` is | ||||
/// set, load files listed in that environment variable instead. | /// set, load files listed in that environment variable instead. | ||||
/// Return errors parsing files. | /// Return errors parsing files. | ||||
fn load_user(&mut self) -> Vec<Error>; | fn load_user(&mut self) -> Vec<Error>; | ||||
/// Load a specified config file. Respect HGPLAIN environment variables. | /// Load a specified config file. Respect HGPLAIN environment variables. | ||||
/// Return errors parsing files. | /// Return errors parsing files. | ||||
fn load_hgrc(&mut self, path: impl AsRef<Path>, source: &'static str) -> Vec<Error>; | fn load_hgrc( | ||||
&mut self, | |||||
path: impl AsRef<Path>, | |||||
source: &'static str, | |||||
) -> Vec<Error>; | |||||
/// Get a config item. Convert to type `T`. | /// Get a config item. Convert to type `T`. | ||||
fn get_opt<T: FromConfigValue>(&self, section: &str, name: &str) -> Result<Option<T>>; | fn get_opt<T: FromConfigValue>( | ||||
&self, | |||||
section: &str, | |||||
name: &str, | |||||
) -> Result<Option<T>>; | |||||
/// Get a config item. Convert to type `T`. | /// Get a config item. Convert to type `T`. | ||||
/// | /// | ||||
/// If the config item is not set, calculate it using `default_func`. | /// If the config item is not set, calculate it using `default_func`. | ||||
fn get_or<T: FromConfigValue>( | fn get_or<T: FromConfigValue>( | ||||
&self, | &self, | ||||
section: &str, | section: &str, | ||||
name: &str, | name: &str, | ||||
default_func: impl Fn() -> T, | default_func: impl Fn() -> T, | ||||
) -> Result<T> { | ) -> Result<T> { | ||||
Ok(self.get_opt(section, name)?.unwrap_or_else(default_func)) | Ok(self.get_opt(section, name)?.unwrap_or_else(default_func)) | ||||
} | } | ||||
/// Get a config item. Convert to type `T`. | /// Get a config item. Convert to type `T`. | ||||
/// | /// | ||||
/// If the config item is not set, return `T::default()`. | /// If the config item is not set, return `T::default()`. | ||||
fn get_or_default<T: Default + FromConfigValue>(&self, section: &str, name: &str) -> Result<T> { | fn get_or_default<T: Default + FromConfigValue>( | ||||
&self, | |||||
section: &str, | |||||
name: &str, | |||||
) -> Result<T> { | |||||
self.get_or(section, name, Default::default) | self.get_or(section, name, Default::default) | ||||
} | } | ||||
} | } | ||||
pub trait FromConfigValue: Sized { | pub trait FromConfigValue: Sized { | ||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self>; | fn try_from_bytes(bytes: &[u8]) -> Result<Self>; | ||||
} | } | ||||
let plain_exceptions: HashSet<String> = plain_except | let plain_exceptions: HashSet<String> = plain_except | ||||
.unwrap_or_else(|_| "".to_string()) | .unwrap_or_else(|_| "".to_string()) | ||||
.split(',') | .split(',') | ||||
.map(|s| s.to_string()) | .map(|s| s.to_string()) | ||||
.collect(); | .collect(); | ||||
// [defaults] and [commands] are always blacklisted. | // [defaults] and [commands] are always blacklisted. | ||||
let mut section_blacklist: HashSet<Bytes> = | let mut section_blacklist: HashSet<Bytes> = | ||||
["defaults", "commands"].iter().map(|&s| s.into()).collect(); | ["defaults", "commands"] | ||||
.iter() | |||||
.map(|&s| s.into()) | |||||
.collect(); | |||||
// [alias], [revsetalias], [templatealias] are blacklisted if they are outside | // [alias], [revsetalias], [templatealias] are blacklisted if | ||||
// HGPLAINEXCEPT. | // they are outside HGPLAINEXCEPT. | ||||
for &name in ["alias", "revsetalias", "templatealias"].iter() { | for &name in ["alias", "revsetalias", "templatealias"].iter() { | ||||
if !plain_exceptions.contains(name) { | if !plain_exceptions.contains(name) { | ||||
section_blacklist.insert(Bytes::from(name)); | section_blacklist.insert(Bytes::from(name)); | ||||
} | } | ||||
} | } | ||||
// These configs under [ui] are always blacklisted. | // These configs under [ui] are always blacklisted. | ||||
let mut ui_blacklist: HashSet<Bytes> = [ | let mut ui_blacklist: HashSet<Bytes> = [ | ||||
"debug", | "debug", | ||||
"fallbackencoding", | "fallbackencoding", | ||||
"quiet", | "quiet", | ||||
"slash", | "slash", | ||||
"logtemplate", | "logtemplate", | ||||
"statuscopies", | "statuscopies", | ||||
"style", | "style", | ||||
"traceback", | "traceback", | ||||
"verbose", | "verbose", | ||||
] | ] | ||||
.iter() | .iter() | ||||
.map(|&s| s.into()) | .map(|&s| s.into()) | ||||
.collect(); | .collect(); | ||||
// exitcodemask is blacklisted if exitcode is outside HGPLAINEXCEPT. | // exitcodemask is blacklisted if exitcode is outside | ||||
// HGPLAINEXCEPT. | |||||
if !plain_exceptions.contains("exitcode") { | if !plain_exceptions.contains("exitcode") { | ||||
ui_blacklist.insert("exitcodemask".into()); | ui_blacklist.insert("exitcodemask".into()); | ||||
} | } | ||||
(section_blacklist, ui_blacklist) | (section_blacklist, ui_blacklist) | ||||
}; | }; | ||||
let filter = move |section: Bytes, name: Bytes, value: Option<Bytes>| { | let filter = | ||||
move |section: Bytes, name: Bytes, value: Option<Bytes>| { | |||||
if section_blacklist.contains(§ion) | if section_blacklist.contains(§ion) | ||||
|| (section.as_ref() == b"ui" && ui_blacklist.contains(&name)) | || (section.as_ref() == b"ui" | ||||
&& ui_blacklist.contains(&name)) | |||||
{ | { | ||||
None | None | ||||
} else { | } else { | ||||
Some((section, name, value)) | Some((section, name, value)) | ||||
} | } | ||||
}; | }; | ||||
self.append_filter(Box::new(filter)) | self.append_filter(Box::new(filter)) | ||||
} else { | } else { | ||||
self | self | ||||
} | } | ||||
} | } | ||||
/// Set section whitelist. Sections outside the whitelist won't be loaded. | /// Set section whitelist. Sections outside the whitelist won't be loaded. | ||||
/// This is implemented via `append_filter`. | /// This is implemented via `append_filter`. | ||||
fn whitelist_sections<B: Clone + Into<Bytes>>(self, sections: Vec<B>) -> Self { | fn whitelist_sections<B: Clone + Into<Bytes>>( | ||||
self, | |||||
sections: Vec<B>, | |||||
) -> Self { | |||||
let whitelist: HashSet<Bytes> = sections | let whitelist: HashSet<Bytes> = sections | ||||
.iter() | .iter() | ||||
.cloned() | .cloned() | ||||
.map(|section| section.into()) | .map(|section| section.into()) | ||||
.collect(); | .collect(); | ||||
let filter = move |section: Bytes, name: Bytes, value: Option<Bytes>| { | let filter = | ||||
move |section: Bytes, name: Bytes, value: Option<Bytes>| { | |||||
if whitelist.contains(§ion) { | if whitelist.contains(§ion) { | ||||
Some((section, name, value)) | Some((section, name, value)) | ||||
} else { | } else { | ||||
None | None | ||||
} | } | ||||
}; | }; | ||||
self.append_filter(Box::new(filter)) | self.append_filter(Box::new(filter)) | ||||
} | } | ||||
/// Set section remap. If a section name matches an entry key, it will be treated as if the | /// Set section remap. If a section name matches an entry key, it will be | ||||
/// name is the entry value. The remap wouldn't happen recursively. For example, with a | /// treated as if the name is the entry value. The remap wouldn't | ||||
/// `{"A": "B", "B": "C"}` map, section name "A" will be treated as "B", not "C". | /// happen recursively. For example, with a `{"A": "B", "B": "C"}` map, | ||||
/// section name "A" will be treated as "B", not "C". | |||||
/// This is implemented via `append_filter`. | /// This is implemented via `append_filter`. | ||||
fn remap_sections<K, V>(self, remap: HashMap<K, V>) -> Self | fn remap_sections<K, V>(self, remap: HashMap<K, V>) -> Self | ||||
where | where | ||||
K: Eq + Hash + Into<Bytes>, | K: Eq + Hash + Into<Bytes>, | ||||
V: Into<Bytes>, | V: Into<Bytes>, | ||||
{ | { | ||||
let remap: HashMap<Bytes, Bytes> = remap | let remap: HashMap<Bytes, Bytes> = remap | ||||
.into_iter() | .into_iter() | ||||
.map(|(k, v)| (k.into(), v.into())) | .map(|(k, v)| (k.into(), v.into())) | ||||
.collect(); | .collect(); | ||||
let filter = move |section: Bytes, name: Bytes, value: Option<Bytes>| { | let filter = | ||||
move |section: Bytes, name: Bytes, value: Option<Bytes>| { | |||||
let section = remap.get(§ion).cloned().unwrap_or(section); | let section = remap.get(§ion).cloned().unwrap_or(section); | ||||
Some((section, name, value)) | Some((section, name, value)) | ||||
}; | }; | ||||
self.append_filter(Box::new(filter)) | self.append_filter(Box::new(filter)) | ||||
} | } | ||||
fn readonly_items<S: Into<Bytes>, N: Into<Bytes>>(self, items: Vec<(S, N)>) -> Self { | fn readonly_items<S: Into<Bytes>, N: Into<Bytes>>( | ||||
self, | |||||
items: Vec<(S, N)>, | |||||
) -> Self { | |||||
let readonly_items: HashSet<(Bytes, Bytes)> = items | let readonly_items: HashSet<(Bytes, Bytes)> = items | ||||
.into_iter() | .into_iter() | ||||
.map(|(section, name)| (section.into(), name.into())) | .map(|(section, name)| (section.into(), name.into())) | ||||
.collect(); | .collect(); | ||||
let filter = move |section: Bytes, name: Bytes, value: Option<Bytes>| { | let filter = | ||||
move |section: Bytes, name: Bytes, value: Option<Bytes>| { | |||||
if readonly_items.contains(&(section.clone(), name.clone())) { | if readonly_items.contains(&(section.clone(), name.clone())) { | ||||
None | None | ||||
} else { | } else { | ||||
Some((section, name, value)) | Some((section, name, value)) | ||||
} | } | ||||
}; | }; | ||||
self.append_filter(Box::new(filter)) | self.append_filter(Box::new(filter)) | ||||
} | } | ||||
} | } | ||||
impl ConfigSetHgExt for ConfigSet { | impl ConfigSetHgExt for ConfigSet { | ||||
fn load_system(&mut self) -> Vec<Error> { | fn load_system(&mut self) -> Vec<Error> { | ||||
let opts = Options::new().source("system").process_hgplain(); | let opts = Options::new().source("system").process_hgplain(); | ||||
let mut errors = Vec::new(); | let mut errors = Vec::new(); | ||||
if env::var(HGRCPATH).is_err() { | if env::var(HGRCPATH).is_err() { | ||||
#[cfg(unix)] | #[cfg(unix)] | ||||
{ | { | ||||
errors.append(&mut self.load_path("/etc/mercurial/system.rc", &opts)); | |||||
// TODO(T40519286): Remove this after the tupperware overrides move out of hgrc.d | |||||
errors.append( | errors.append( | ||||
&mut self.load_path("/etc/mercurial/hgrc.d/tupperware_overrides.rc", &opts), | &mut self.load_path("/etc/mercurial/system.rc", &opts), | ||||
); | |||||
// TODO(T40519286): Remove this after the tupperware overrides | |||||
// move out of hgrc.d | |||||
errors.append(&mut self.load_path( | |||||
"/etc/mercurial/hgrc.d/tupperware_overrides.rc", | |||||
&opts, | |||||
)); | |||||
// TODO(quark): Remove this after packages using system.rc are | |||||
// rolled out | |||||
errors.append( | |||||
&mut self | |||||
.load_path("/etc/mercurial/hgrc.d/include.rc", &opts), | |||||
); | ); | ||||
// TODO(quark): Remove this after packages using system.rc are rolled out | |||||
errors.append(&mut self.load_path("/etc/mercurial/hgrc.d/include.rc", &opts)); | |||||
} | } | ||||
#[cfg(windows)] | #[cfg(windows)] | ||||
{ | { | ||||
if let Ok(program_data_path) = env::var("PROGRAMDATA") { | if let Ok(program_data_path) = env::var("PROGRAMDATA") { | ||||
use std::path::Path; | use std::path::Path; | ||||
let hgrc_dir = Path::new(&program_data_path).join("Facebook\\Mercurial"); | let hgrc_dir = Path::new(&program_data_path) | ||||
errors.append(&mut self.load_path(hgrc_dir.join("system.rc"), &opts)); | .join("Facebook\\Mercurial"); | ||||
// TODO(quark): Remove this after packages using system.rc are rolled out | errors.append( | ||||
errors.append(&mut self.load_path(hgrc_dir.join("hgrc"), &opts)); | &mut self.load_path(hgrc_dir.join("system.rc"), &opts), | ||||
); | |||||
// TODO(quark): Remove this after packages using system.rc | |||||
// are rolled out | |||||
errors.append( | |||||
&mut self.load_path(hgrc_dir.join("hgrc"), &opts), | |||||
); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
errors | errors | ||||
} | } | ||||
fn load_user(&mut self) -> Vec<Error> { | fn load_user(&mut self) -> Vec<Error> { | ||||
let paths = rcpath.split(':'); | let paths = rcpath.split(':'); | ||||
#[cfg(windows)] | #[cfg(windows)] | ||||
let paths = rcpath.split(';'); | let paths = rcpath.split(';'); | ||||
for path in paths { | for path in paths { | ||||
errors.append(&mut self.load_path(expand_path(path), &opts)); | errors.append(&mut self.load_path(expand_path(path), &opts)); | ||||
} | } | ||||
} else { | } else { | ||||
if let Some(home_dir) = dirs::home_dir() { | if let Some(home_dir) = dirs::home_dir() { | ||||
errors.append(&mut self.load_path(home_dir.join(".hgrc"), &opts)); | errors.append( | ||||
&mut self.load_path(home_dir.join(".hgrc"), &opts), | |||||
); | |||||
#[cfg(windows)] | #[cfg(windows)] | ||||
{ | { | ||||
errors.append(&mut self.load_path(home_dir.join("mercurial.ini"), &opts)); | errors.append( | ||||
&mut self | |||||
.load_path(home_dir.join("mercurial.ini"), &opts), | |||||
); | |||||
} | } | ||||
} | } | ||||
if let Some(config_dir) = dirs::config_dir() { | if let Some(config_dir) = dirs::config_dir() { | ||||
errors.append(&mut self.load_path(config_dir.join("hg/hgrc"), &opts)); | errors.append( | ||||
&mut self.load_path(config_dir.join("hg/hgrc"), &opts), | |||||
); | |||||
} | } | ||||
} | } | ||||
errors | errors | ||||
} | } | ||||
fn load_hgrc(&mut self, path: impl AsRef<Path>, source: &'static str) -> Vec<Error> { | fn load_hgrc( | ||||
&mut self, | |||||
path: impl AsRef<Path>, | |||||
source: &'static str, | |||||
) -> Vec<Error> { | |||||
let opts = Options::new().source(source).process_hgplain(); | let opts = Options::new().source(source).process_hgplain(); | ||||
self.load_path(path, &opts) | self.load_path(path, &opts) | ||||
} | } | ||||
fn get_opt<T: FromConfigValue>(&self, section: &str, name: &str) -> Result<Option<T>> { | fn get_opt<T: FromConfigValue>( | ||||
&self, | |||||
section: &str, | |||||
name: &str, | |||||
) -> Result<Option<T>> { | |||||
ConfigSet::get(self, section, name) | ConfigSet::get(self, section, name) | ||||
.map(|bytes| T::try_from_bytes(&bytes)) | .map(|bytes| T::try_from_bytes(&bytes)) | ||||
.transpose() | .transpose() | ||||
} | } | ||||
} | } | ||||
impl FromConfigValue for bool { | impl FromConfigValue for bool { | ||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | ||||
let value = std::str::from_utf8(bytes)?.to_lowercase(); | let value = std::str::from_utf8(bytes)?.to_lowercase(); | ||||
match value.as_ref() { | match value.as_ref() { | ||||
"1" | "yes" | "true" | "on" | "always" => Ok(true), | "1" | "yes" | "true" | "on" | "always" => Ok(true), | ||||
"0" | "no" | "false" | "off" | "never" => Ok(false), | "0" | "no" | "false" | "off" | "never" => Ok(false), | ||||
_ => Err(Error::Convert(format!("invalid bool: {}", value)).into()), | _ => { | ||||
Err(Error::Convert(format!("invalid bool: {}", value)).into()) | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
impl FromConfigValue for i8 { | impl FromConfigValue for i8 { | ||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | ||||
let value = std::str::from_utf8(bytes)?.parse()?; | let value = std::str::from_utf8(bytes)?.parse()?; | ||||
Ok(value) | Ok(value) | ||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | ||||
let value = std::str::from_utf8(bytes)?.parse()?; | let value = std::str::from_utf8(bytes)?.parse()?; | ||||
Ok(value) | Ok(value) | ||||
} | } | ||||
} | } | ||||
impl FromConfigValue for String { | impl FromConfigValue for String { | ||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | ||||
String::from_utf8(bytes.to_vec()) | String::from_utf8(bytes.to_vec()).map_err(|_| { | ||||
.map_err(|_| Error::Convert(format!("{:?} is not utf8 encoded", bytes)).into()) | Error::Convert(format!("{:?} is not utf8 encoded", bytes)).into() | ||||
}) | |||||
} | } | ||||
} | } | ||||
/// Byte count specified with a unit. For example: `1.5 MB`. | /// Byte count specified with a unit. For example: `1.5 MB`. | ||||
#[derive(Copy, Clone, Default)] | #[derive(Copy, Clone, Default)] | ||||
pub struct ByteCount(u64); | pub struct ByteCount(u64); | ||||
impl ByteCount { | impl ByteCount { | ||||
("t", 1 << 40), | ("t", 1 << 40), | ||||
("b", 1), | ("b", 1), | ||||
("", 1), | ("", 1), | ||||
]; | ]; | ||||
let value = std::str::from_utf8(bytes)?.to_lowercase(); | let value = std::str::from_utf8(bytes)?.to_lowercase(); | ||||
for (suffix, unit) in sizeunits.iter() { | for (suffix, unit) in sizeunits.iter() { | ||||
if value.ends_with(suffix) { | if value.ends_with(suffix) { | ||||
let number_str: &str = value[..value.len() - suffix.len()].trim(); | let number_str: &str = | ||||
value[..value.len() - suffix.len()].trim(); | |||||
let number: f64 = number_str.parse()?; | let number: f64 = number_str.parse()?; | ||||
if number < 0.0 { | if number < 0.0 { | ||||
return Err(Error::Convert(format!( | return Err(Error::Convert(format!( | ||||
"byte size '{:?}' cannot be negative", | "byte size '{:?}' cannot be negative", | ||||
value | value | ||||
)) | )) | ||||
.into()); | .into()); | ||||
} | } | ||||
let unit = *unit as f64; | let unit = *unit as f64; | ||||
return Ok(ByteCount((number * unit) as u64)); | return Ok(ByteCount((number * unit) as u64)); | ||||
} | } | ||||
} | } | ||||
Err(Error::Convert(format!("'{:?}' cannot be parsed as a byte size", value)).into()) | Err(Error::Convert(format!( | ||||
"'{:?}' cannot be parsed as a byte size", | |||||
value | |||||
)) | |||||
.into()) | |||||
} | } | ||||
} | } | ||||
impl FromConfigValue for PathBuf { | impl FromConfigValue for PathBuf { | ||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | fn try_from_bytes(bytes: &[u8]) -> Result<Self> { | ||||
let st = std::str::from_utf8(&bytes)?; | let st = std::str::from_utf8(&bytes)?; | ||||
Ok(expand_path(st)) | Ok(expand_path(st)) | ||||
// elif s[offset:offset + 1] == '"' and parts[-1][-1:] == '\\': | // elif s[offset:offset + 1] == '"' and parts[-1][-1:] == '\\': | ||||
// parts[-1] = parts[-1][:-1] + s[offset:offset + 1] | // parts[-1] = parts[-1][:-1] + s[offset:offset + 1] | ||||
// return _parse_plain, parts, offset + 1 | // return _parse_plain, parts, offset + 1 | ||||
// parts[-1] += s[offset:offset + 1] | // parts[-1] += s[offset:offset + 1] | ||||
// return _parse_plain, parts, offset + 1 | // return _parse_plain, parts, offset + 1 | ||||
// ``` | // ``` | ||||
State::Plain => { | State::Plain => { | ||||
let mut whitespace = false; | let mut whitespace = false; | ||||
while offset < value.len() && b" \n\r\t,".contains(&value[offset]) { | while offset < value.len() | ||||
&& b" \n\r\t,".contains(&value[offset]) | |||||
{ | |||||
whitespace = true; | whitespace = true; | ||||
offset += 1; | offset += 1; | ||||
} | } | ||||
if offset >= value.len() { | if offset >= value.len() { | ||||
break; | break; | ||||
} | } | ||||
if whitespace { | if whitespace { | ||||
parts.push(Vec::new()); | parts.push(Vec::new()); | ||||
// else: | // else: | ||||
// return None, parts, offset | // return None, parts, offset | ||||
// return _parse_plain, parts, offset | // return _parse_plain, parts, offset | ||||
// ``` | // ``` | ||||
State::Quote => { | State::Quote => { | ||||
if offset < value.len() && value[offset] == b'"' { | if offset < value.len() && value[offset] == b'"' { | ||||
parts.push(Vec::new()); | parts.push(Vec::new()); | ||||
offset += 1; | offset += 1; | ||||
while offset < value.len() && b" \n\r\t,".contains(&value[offset]) { | while offset < value.len() | ||||
&& b" \n\r\t,".contains(&value[offset]) | |||||
{ | |||||
offset += 1; | offset += 1; | ||||
} | } | ||||
state = State::Plain; | state = State::Plain; | ||||
continue; | continue; | ||||
} | } | ||||
while offset < value.len() && value[offset] != b'"' { | while offset < value.len() && value[offset] != b'"' { | ||||
if value[offset] == b'\\' | if value[offset] == b'\\' | ||||
&& offset + 1 < value.len() | && offset + 1 < value.len() | ||||
&& value[offset + 1] == b'"' | && value[offset + 1] == b'"' | ||||
{ | { | ||||
offset += 1; | offset += 1; | ||||
parts.last_mut().unwrap().push(b'"'); | parts.last_mut().unwrap().push(b'"'); | ||||
} else { | } else { | ||||
parts.last_mut().unwrap().push(value[offset]); | parts.last_mut().unwrap().push(value[offset]); | ||||
} | } | ||||
offset += 1; | offset += 1; | ||||
} | } | ||||
if offset >= value.len() { | if offset >= value.len() { | ||||
let mut real_parts: Vec<Vec<u8>> = parse_list_internal(parts.last().unwrap()) | let mut real_parts: Vec<Vec<u8>> = | ||||
parse_list_internal(parts.last().unwrap()) | |||||
.iter() | .iter() | ||||
.map(|b| b.to_vec()) | .map(|b| b.to_vec()) | ||||
.collect(); | .collect(); | ||||
if real_parts.is_empty() { | if real_parts.is_empty() { | ||||
parts.pop(); | parts.pop(); | ||||
parts.push(vec![b'"']); | parts.push(vec![b'"']); | ||||
} else { | } else { | ||||
real_parts[0].insert(0, b'"'); | real_parts[0].insert(0, b'"'); | ||||
parts.pop(); | parts.pop(); | ||||
parts.append(&mut real_parts); | parts.append(&mut real_parts); | ||||
} | } | ||||
assert_eq!( | assert_eq!( | ||||
parse_list(b"\"hello world\", \"how are you?\""), | parse_list(b"\"hello world\", \"how are you?\""), | ||||
vec![b("hello world"), b("how are you?")] | vec![b("hello world"), b("how are you?")] | ||||
); | ); | ||||
assert_eq!( | assert_eq!( | ||||
parse_list(b"Do\"Not\"Separate"), | parse_list(b"Do\"Not\"Separate"), | ||||
vec![b("Do\"Not\"Separate")] | vec![b("Do\"Not\"Separate")] | ||||
); | ); | ||||
assert_eq!(parse_list(b"\"Do\"Separate"), vec![b("Do"), b("Separate")]); | assert_eq!( | ||||
parse_list(b"\"Do\"Separate"), | |||||
vec![b("Do"), b("Separate")] | |||||
); | |||||
assert_eq!( | assert_eq!( | ||||
parse_list(b"\"Do\\\"NotSeparate\""), | parse_list(b"\"Do\\\"NotSeparate\""), | ||||
vec![b("Do\"NotSeparate")] | vec![b("Do\"NotSeparate")] | ||||
); | ); | ||||
assert_eq!( | assert_eq!( | ||||
parse_list(&b"string \"with extraneous\" quotation mark\""[..]), | parse_list(&b"string \"with extraneous\" quotation mark\""[..]), | ||||
vec![ | vec![ | ||||
b("string"), | b("string"), | ||||
vec![b(""), b(" key = "), b("x\""), b("y"), b(""), b("\"")] | vec![b(""), b(" key = "), b("x\""), b("y"), b(""), b("\"")] | ||||
); | ); | ||||
assert_eq!(parse_list(b",,,, "), Vec::<Bytes>::new()); | assert_eq!(parse_list(b",,,, "), Vec::<Bytes>::new()); | ||||
assert_eq!( | assert_eq!( | ||||
parse_list(b"\" just with starting quotation"), | parse_list(b"\" just with starting quotation"), | ||||
vec![b("\""), b("just"), b("with"), b("starting"), b("quotation")] | vec![b("\""), b("just"), b("with"), b("starting"), b("quotation")] | ||||
); | ); | ||||
assert_eq!( | assert_eq!( | ||||
parse_list(&b"\"longer quotation\" with \"no ending quotation"[..]), | parse_list( | ||||
&b"\"longer quotation\" with \"no ending quotation"[..] | |||||
), | |||||
vec![ | vec![ | ||||
b("longer quotation"), | b("longer quotation"), | ||||
b("with"), | b("with"), | ||||
b("\"no"), | b("\"no"), | ||||
b("ending"), | b("ending"), | ||||
b("quotation"), | b("quotation"), | ||||
] | ] | ||||
); | ); | ||||
assert_eq!( | assert_eq!( | ||||
parse_list(&b"this is \\\" \"not a quotation mark\""[..]), | parse_list(&b"this is \\\" \"not a quotation mark\""[..]), | ||||
vec![b("this"), b("is"), b("\""), b("not a quotation mark")] | vec![b("this"), b("is"), b("\""), b("not a quotation mark")] | ||||
); | ); | ||||
assert_eq!(parse_list(b"\n \n\nding\ndong"), vec![b("ding"), b("dong")]); | assert_eq!( | ||||
parse_list(b"\n \n\nding\ndong"), | |||||
vec![b("ding"), b("dong")] | |||||
); | |||||
// Other manually written cases | // Other manually written cases | ||||
assert_eq!(parse_list("a,b,,c"), vec![b("a"), b("b"), b("c")]); | assert_eq!(parse_list("a,b,,c"), vec![b("a"), b("b"), b("c")]); | ||||
assert_eq!(parse_list("a b c"), vec![b("a"), b("b"), b("c")]); | assert_eq!(parse_list("a b c"), vec![b("a"), b("b"), b("c")]); | ||||
assert_eq!( | assert_eq!( | ||||
parse_list(" , a , , b, , c , "), | parse_list(" , a , , b, , c , "), | ||||
vec![b("a"), b("b"), b("c")] | vec![b("a"), b("b"), b("c")] | ||||
); | ); |
/* | /* | ||||
* Copyright (c) Facebook, Inc. and its affiliates. | * Copyright (c) Facebook, Inc. and its affiliates. | ||||
* | * | ||||
* This software may be used and distributed according to the terms of the | * This software may be used and distributed according to the terms of the | ||||
* GNU General Public License version 2. | * GNU General Public License version 2. | ||||
*/ | */ | ||||
//! # ConfigParser | //! # ConfigParser | ||||
//! | //! | ||||
//! ConfigParser is a utility to parse hgrc-like config files. | //! ConfigParser is a utility to parse hgrc-like config files. | ||||
//! | //! | ||||
//! ## Features | //! ## Features | ||||
//! | //! | ||||
//! - Parse valid hgrc-like config files efficiently. | //! - Parse valid hgrc-like config files efficiently. | ||||
//! - Track source locations of config values. Keep multiple locations of | //! - Track source locations of config values. Keep multiple locations of a | ||||
//! a same config if it is overridden. | //! same config if it is overridden. | ||||
//! | //! | ||||
//! ## Config Format | //! ## Config Format | ||||
//! | //! | ||||
//! hgrc files are similar to INI files: | //! hgrc files are similar to INI files: | ||||
//! | //! | ||||
//! ```plain,ignore | //! ```plain,ignore | ||||
//! [section1] | //! [section1] | ||||
//! name1 = value1 | //! name1 = value1 |