##// END OF EJS Templates
revlog: change _addrevision to return the new revision...
revlog: change _addrevision to return the new revision The node is passed as argument already, so returning it is quite pointless. The revision number on the other is useful as it decouples the caller from the revlog internals. Differential Revision: https://phab.mercurial-scm.org/D9880

File last commit:

r47233:fb0ad038 default
r47234:9f8f0df3 default
Show More
layer.rs
291 lines | 9.6 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
rust: use HgError in ConfigError...
r47176 use crate::errors::{HgError, IoResultExt};
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
rust: Use the DisplayBytes trait in config printing...
r47227 use 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...
r47232 /// 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...
r47233 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...
r47232 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 {
Err(HgError::abort(format!(
"malformed --config option: \"{}\" \
(use --config section.name=value)",
String::from_utf8_lossy(arg),
)))?
}
}
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)?)
}
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() {
if let Some(m) = INCLUDE_RE.captures(&bytes) {
let filename_bytes = &m[1];
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));
let data = std::fs::read(&filename).for_file(&filename)?;
Simon Sapin
rust: use HgError in ConfigError...
r47176 layers.push(current_layer);
Simon Sapin
rust: Remove unnecessary check for absolute path before joining...
r47211 layers.extend(Self::parse(&filename, &data)?);
Simon Sapin
rust: use HgError in ConfigError...
r47176 current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
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();
}
current_layer.add(
section.clone(),
item,
value,
Some(index + 1),
);
} 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
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()),
line: Some(index + 1),
bytes: bytes.to_owned(),
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...
r47227 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...
r47227 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>,
}
#[derive(Clone, Debug)]
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),
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// From a `--config` CLI argument
CommandLine,
/// From environment variables like `$PAGER` or `$EDITOR`
Raphaël Gomès
hg-core: add basic config module...
r46803 Environment(Vec<u8>),
/* TODO cli
* TODO defaults (configitems.py)
* TODO extensions
* TODO Python resources?
* Others? */
}
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47227 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...
r47227 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
ConfigOrigin::CommandLine => out.write_all(b"--config"),
ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
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>,
pub bytes: Vec<u8>,
}
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")
}