##// END OF EJS Templates
check-code: drop the check for whitespace around named parameters...
check-code: drop the check for whitespace around named parameters This check flags py3 annotations of named parameters, because `black` adds spaces around the assignment in this case. Since the chosen formatter has opinions (and pylint also wants the space in the case of annotations), drop the check so we can use py3 annotations.

File last commit:

r50409:e37416d4 default
r50465:3a2b6158 default
Show More
layer.rs
349 lines | 11.8 KiB | application/rls-services+xml | RustLexer
Raphaël Gomès
hg-core: add basic config module...
r46803 // layer.rs
//
// Copyright 2020
// Valentin Gatien-Baron,
// Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 use crate::errors::HgError;
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 use crate::exit_codes::CONFIG_PARSE_ERROR_ABORT;
Simon Sapin
rust: replace read_whole_file with std::fs::read...
r47210 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 use format_bytes::{format_bytes, write_bytes, DisplayBytes};
Raphaël Gomès
hg-core: add basic config module...
r46803 use lazy_static::lazy_static;
use regex::bytes::Regex;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
lazy_static! {
static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
/// Continuation whitespace
static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
/// A directive that allows for removing previous entries
static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
/// A directive that allows for including other config files
static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
}
/// All config values separated by layers of precedence.
/// Each config source may be split in multiple layers if `%include` directives
/// are used.
/// TODO detail the general precedence
#[derive(Clone)]
pub struct ConfigLayer {
/// Mapping of the sections to their items
sections: HashMap<Vec<u8>, ConfigItem>,
/// All sections (and their items/values) in a layer share the same origin
pub origin: ConfigOrigin,
/// Whether this layer comes from a trusted user or group
pub trusted: bool,
}
impl ConfigLayer {
pub fn new(origin: ConfigOrigin) -> Self {
ConfigLayer {
sections: HashMap::new(),
trusted: true, // TODO check
origin,
}
}
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 /// Parse `--config` CLI arguments and return a layer if there’s any
pub(crate) fn parse_cli_args(
cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
) -> Result<Option<Self>, ConfigError> {
fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
use crate::utils::SliceExt;
Simon Sapin
rhg: add limited support for the `config` sub-command...
r47255 let (section_and_item, value) = arg.split_2(b'=')?;
let (section, item) = section_and_item.trim().split_2(b'.')?;
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 Some((
section.to_owned(),
item.to_owned(),
value.trim().to_owned(),
))
}
let mut layer = Self::new(ConfigOrigin::CommandLine);
for arg in cli_config_args {
let arg = arg.as_ref();
if let Some((section, item, value)) = parse_one(arg) {
layer.add(section, item, value, None);
} else {
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 Err(HgError::abort(
format!(
"abort: malformed --config option: '{}' \
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 (use --config section.name=value)",
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 String::from_utf8_lossy(arg),
),
CONFIG_PARSE_ERROR_ABORT,
Raphaël Gomès
rust: add support for hints in error messages...
r50382 None,
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 ))?
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 }
}
if layer.sections.is_empty() {
Ok(None)
} else {
Ok(Some(layer))
}
}
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// Returns whether this layer comes from `--config` CLI arguments
pub(crate) fn is_from_command_line(&self) -> bool {
if let ConfigOrigin::CommandLine = self.origin {
true
} else {
false
}
}
Raphaël Gomès
hg-core: add basic config module...
r46803 /// Add an entry to the config, overwriting the old one if already present.
pub fn add(
&mut self,
section: Vec<u8>,
item: Vec<u8>,
value: Vec<u8>,
line: Option<usize>,
) {
self.sections
.entry(section)
.or_insert_with(|| HashMap::new())
.insert(item, ConfigValue { bytes: value, line });
}
/// Returns the config value in `<section>.<item>` if it exists
pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
Some(self.sections.get(section)?.get(item)?)
}
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 /// Returns the keys defined in the given section
pub fn iter_keys(&self, section: &[u8]) -> impl Iterator<Item = &[u8]> {
self.sections
.get(section)
.into_iter()
.flat_map(|section| section.keys().map(|vec| &**vec))
}
Simon Sapin
rhg: Add support for ui.ignore and ui.ignore.* config...
r49282 /// Returns the (key, value) pairs defined in the given section
pub fn iter_section<'layer>(
&'layer self,
section: &[u8],
) -> impl Iterator<Item = (&'layer [u8], &'layer [u8])> {
self.sections
.get(section)
.into_iter()
.flat_map(|section| section.iter().map(|(k, v)| (&**k, &*v.bytes)))
}
Simon Sapin
rhg: [encode] and [decode] config sections are not supported...
r49162 /// Returns whether any key is defined in the given section
pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
self.sections
.get(section)
.map_or(false, |section| !section.is_empty())
}
Raphaël Gomès
hg-core: add basic config module...
r46803 pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
/// Returns a `Vec` of layers in order of precedence (so, in read order),
/// recursively parsing the `%include` directives if any.
pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
let mut layers = vec![];
// Discard byte order mark if any
let data = if data.starts_with(b"\xef\xbb\xbf") {
&data[3..]
} else {
data
};
// TODO check if it's trusted
let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
let mut lines_iter =
data.split(|b| *b == b'\n').enumerate().peekable();
let mut section = b"".to_vec();
while let Some((index, bytes)) = lines_iter.next() {
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 let line = Some(index + 1);
Raphaël Gomès
hg-core: add basic config module...
r46803 if let Some(m) = INCLUDE_RE.captures(&bytes) {
let filename_bytes = &m[1];
Simon Sapin
rhg: Add support for environment variables in config include paths...
r47476 let filename_bytes = crate::utils::expand_vars(filename_bytes);
Simon Sapin
rust: Remove unnecessary check for absolute path before joining...
r47211 // `Path::parent` only fails for the root directory,
Simon Sapin
rhg: Parse per-repository configuration...
r47215 // which `src` can’t be since we’ve managed to open it as a
// file.
Simon Sapin
rust: Remove unnecessary check for absolute path before joining...
r47211 let dir = src
.parent()
.expect("Path::parent fail on a file we’ve read");
Simon Sapin
rhg: Parse per-repository configuration...
r47215 // `Path::join` with an absolute argument correctly ignores the
// base path
Simon Sapin
rust: Remove unnecessary check for absolute path before joining...
r47211 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
Simon Sapin
rhg: Silently ignore missing files in config %include...
r47477 match std::fs::read(&filename) {
Ok(data) => {
layers.push(current_layer);
layers.extend(Self::parse(&filename, &data)?);
current_layer =
Self::new(ConfigOrigin::File(src.to_owned()));
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 }
Simon Sapin
rhg: Silently ignore missing files in config %include...
r47477 Err(error) => {
if error.kind() != std::io::ErrorKind::NotFound {
return Err(ConfigParseError {
origin: ConfigOrigin::File(src.to_owned()),
line,
message: format_bytes!(
b"cannot include {} ({})",
filename_bytes,
format_bytes::Utf8(error)
),
}
.into());
}
}
}
Raphaël Gomès
hg-core: add basic config module...
r46803 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
} else if let Some(m) = SECTION_RE.captures(&bytes) {
section = m[1].to_vec();
} else if let Some(m) = ITEM_RE.captures(&bytes) {
let item = m[1].to_vec();
let mut value = m[2].to_vec();
loop {
match lines_iter.peek() {
None => break,
Some((_, v)) => {
if let Some(_) = COMMENT_RE.captures(&v) {
} else if let Some(_) = CONT_RE.captures(&v) {
value.extend(b"\n");
value.extend(&m[1]);
} else {
break;
}
}
};
lines_iter.next();
}
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 current_layer.add(section.clone(), item, value, line);
Raphaël Gomès
hg-core: add basic config module...
r46803 } else if let Some(m) = UNSET_RE.captures(&bytes) {
if let Some(map) = current_layer.sections.get_mut(&section) {
map.remove(&m[1]);
}
} else {
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 let message = if bytes.starts_with(b" ") {
format_bytes!(b"unexpected leading whitespace: {}", bytes)
} else {
bytes.to_owned()
};
Simon Sapin
rust: use HgError in ConfigError...
r47176 return Err(ConfigParseError {
Raphaël Gomès
hg-core: add basic config module...
r46803 origin: ConfigOrigin::File(src.to_owned()),
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 line,
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 message,
Simon Sapin
rust: use HgError in ConfigError...
r47176 }
.into());
Raphaël Gomès
hg-core: add basic config module...
r46803 }
}
if !current_layer.is_empty() {
layers.push(current_layer);
}
Ok(layers)
}
}
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 impl DisplayBytes for ConfigLayer {
fn display_bytes(
&self,
out: &mut dyn std::io::Write,
) -> std::io::Result<()> {
Raphaël Gomès
hg-core: add basic config module...
r46803 let mut sections: Vec<_> = self.sections.iter().collect();
sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
for (section, items) in sections.into_iter() {
let mut items: Vec<_> = items.into_iter().collect();
items.sort_by(|e0, e1| e0.0.cmp(e1.0));
for (item, config_entry) in items {
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 write_bytes!(
out,
b"{}.{}={} # {}\n",
section,
item,
&config_entry.bytes,
&self.origin,
Raphaël Gomès
hg-core: add basic config module...
r46803 )?
}
}
Ok(())
}
}
/// Mapping of section item to value.
/// In the following:
/// ```text
/// [ui]
/// paginate=no
/// ```
/// "paginate" is the section item and "no" the value.
pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
#[derive(Clone, Debug, PartialEq)]
pub struct ConfigValue {
/// The raw bytes of the value (be it from the CLI, env or from a file)
pub bytes: Vec<u8>,
/// Only present if the value comes from a file, 1-indexed.
pub line: Option<usize>,
}
Simon Sapin
rhg: Add support for colored output...
r49584 #[derive(Clone, Debug, PartialEq, Eq)]
Raphaël Gomès
hg-core: add basic config module...
r46803 pub enum ConfigOrigin {
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// From a configuration file
Raphaël Gomès
hg-core: add basic config module...
r46803 File(PathBuf),
Arseniy Alekseyev
rhg: support tweakdefaults
r50409 /// From [ui.tweakdefaults]
Tweakdefaults,
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// From a `--config` CLI argument
CommandLine,
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 /// From a `--color` CLI argument
CommandLineColor,
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// From environment variables like `$PAGER` or `$EDITOR`
Raphaël Gomès
hg-core: add basic config module...
r46803 Environment(Vec<u8>),
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 /* TODO defaults (configitems.py)
Raphaël Gomès
hg-core: add basic config module...
r46803 * TODO extensions
* TODO Python resources?
* Others? */
}
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 impl DisplayBytes for ConfigOrigin {
fn display_bytes(
&self,
out: &mut dyn std::io::Write,
) -> std::io::Result<()> {
Raphaël Gomès
hg-core: add basic config module...
r46803 match self {
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
ConfigOrigin::CommandLine => out.write_all(b"--config"),
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 ConfigOrigin::CommandLineColor => out.write_all(b"--color"),
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
Arseniy Alekseyev
rhg: support tweakdefaults
r50409 ConfigOrigin::Tweakdefaults => {
write_bytes!(out, b"ui.tweakdefaults")
}
Raphaël Gomès
hg-core: add basic config module...
r46803 }
}
}
Simon Sapin
rust: use HgError in ConfigError...
r47176 #[derive(Debug)]
pub struct ConfigParseError {
pub origin: ConfigOrigin,
pub line: Option<usize>,
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 pub message: Vec<u8>,
Simon Sapin
rust: use HgError in ConfigError...
r47176 }
Simon Sapin
rust: replace trivial `impl From …` with `#[derive(derive_more::From)]`...
r47164 #[derive(Debug, derive_more::From)]
Raphaël Gomès
hg-core: add basic config module...
r46803 pub enum ConfigError {
Simon Sapin
rust: use HgError in ConfigError...
r47176 Parse(ConfigParseError),
Other(HgError),
Raphaël Gomès
hg-core: add basic config module...
r46803 }
fn make_regex(pattern: &'static str) -> Regex {
Regex::new(pattern).expect("expected a valid regex")
}