configparser: move conversion related to a separated module

Summary:
Move these conversion related function and trait out of `hg` module so EdenFS can use it too. Changes:

* Moved `get_opt`, `get_or` and `get_or_default` directly into `ConfigSet`.
* Moved `FromConfigValue` and `ByteCount` into `configparser::convert`.

Reviewed By: quark-zju

Differential Revision: D26355403

fbshipit-source-id: 9096b7b737bc4a0cccee1a3883e89a323f864fac
This commit is contained in:
Zeyi (Rice) Fan 2021-02-12 12:30:49 -08:00 committed by Facebook GitHub Bot
parent c601c6a46d
commit d552144478
18 changed files with 622 additions and 617 deletions

View File

@ -5,7 +5,6 @@ edition = "2018"
[dependencies]
anyhow = "1.0"
bytes = "0.5"
configparser = { path = "../../../../lib/configparser" }
cpython = { version = "0.5", default-features = false }
cpython-ext = { path = "../../../../lib/cpython-ext", default-features = false }

View File

@ -13,8 +13,9 @@ use cpython::*;
use configparser::{
config::{ConfigSet, Options},
convert::parse_list,
dynamicconfig::Generator,
hg::{generate_dynamicconfig, parse_list, ConfigSetHgExt, OptionsHgExt},
hg::{generate_dynamicconfig, ConfigSetHgExt, OptionsHgExt},
};
use cpython_ext::{error::ResultPyErrExt, PyNone, PyPath, PyPathBuf, Str};

View File

@ -9,7 +9,6 @@ use crate::remotestore::FakeRemoteStore;
use crate::treecontentstore::TreeContentStore;
use crate::utils::key_from_path_node_slice;
use anyhow::Result;
use configparser::hg::ConfigSetHgExt;
use edenapi::{Builder as EdenApiBuilder, EdenApi};
use log::warn;
use manifest::{List, Manifest};

View File

@ -18,6 +18,3 @@ thiserror = "1.0.5"
thrift-types = { path = "../thrift-types" }
tracing = "0.1"
util = { path = "../util" }
[dev-dependencies]
tempfile = "3.0.7"

View File

@ -14,7 +14,6 @@ use anyhow::Error;
use cliparser::alias::{expand_aliases, find_command_name};
use cliparser::parser::{ParseError, ParseOptions, ParseOutput, StructFlags};
use configparser::config::ConfigSet;
use configparser::hg::ConfigSetHgExt;
use std::convert::TryInto;
use std::sync::atomic::Ordering::SeqCst;
use std::{env, path::Path};
@ -104,7 +103,7 @@ fn initialize_blackbox(optional_repo: &OptionalRepo) -> Result<()> {
let config = repo.config();
let max_size = config
.get_or("blackbox", "maxsize", || {
configparser::hg::ByteCount::from(1u64 << 12)
configparser::convert::ByteCount::from(1u64 << 12)
})?
.value();
let max_files = config.get_or("blackbox", "maxfiles", || 3)?;

View File

@ -5,7 +5,7 @@
* GNU General Public License version 2.
*/
use configparser::{config::ConfigSet, hg::ConfigSetHgExt};
use configparser::config::ConfigSet;
use pipe::pipe;
use std::any::Any;
use std::io;

View File

@ -15,11 +15,13 @@ use std::path::{Path, PathBuf};
use std::str;
use std::sync::Arc;
use anyhow::Result;
use indexmap::IndexMap;
use minibytes::Text;
use pest::{self, Parser, Span};
use util::path::expand_path;
use crate::convert::FromConfigValue;
use crate::error::Error;
use crate::parser::{ConfigParser, Rule};
@ -152,6 +154,36 @@ impl ConfigSet {
.unwrap_or_default()
}
/// Get a config item. Convert to type `T`.
pub fn get_opt<T: FromConfigValue>(&self, section: &str, name: &str) -> Result<Option<T>> {
self.get(section, name)
.map(|bytes| T::try_from_str(&bytes))
.transpose()
}
/// Get a config item. Convert to type `T`.
///
/// If the config item is not set, calculate it using `default_func`.
pub fn get_or<T: FromConfigValue>(
&self,
section: &str,
name: &str,
default_func: impl Fn() -> T,
) -> Result<T> {
Ok(self.get_opt(section, name)?.unwrap_or_else(default_func))
}
/// Get a config item. Convert to type `T`.
///
/// If the config item is not set, return `T::default()`.
pub fn get_or_default<T: Default + FromConfigValue>(
&self,
section: &str,
name: &str,
) -> Result<T> {
self.get_or(section, name, Default::default)
}
/// Set a config item directly. `section`, `name` locates the config. `value` is the new value.
/// `source` is some annotation about who set it, ex. "reporc", "userrc", "--config", etc.
pub fn set(
@ -632,6 +664,7 @@ impl SupersetVerification {
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::convert::ByteCount;
use std::io::Write;
use tempdir::TempDir;
@ -1333,4 +1366,73 @@ space_list=value1.a value1.b
None,
);
}
#[test]
fn test_get_or() {
let mut cfg = ConfigSet::new();
cfg.parse(
"[foo]\n\
bool1 = yes\n\
bool2 = unknown\n\
bools = 1, TRUE, On, aLwAys, 0, false, oFF, never\n\
int1 = -33\n\
list1 = x y z\n\
list3 = 2, 3, 1\n\
byte1 = 1.5 KB\n\
byte2 = 500\n\
byte3 = 0.125M\n\
float = 1.42\n\
",
&"test".into(),
);
assert_eq!(cfg.get_or("foo", "bar", || 3).unwrap(), 3);
assert_eq!(cfg.get_or("foo", "bool1", || false).unwrap(), true);
assert_eq!(
format!("{}", cfg.get_or("foo", "bool2", || true).unwrap_err()),
"invalid bool: unknown"
);
assert_eq!(cfg.get_or("foo", "int1", || 42).unwrap(), -33);
assert_eq!(
cfg.get_or("foo", "list1", || vec!["x".to_string()])
.unwrap(),
vec!["x", "y", "z"]
);
assert_eq!(
cfg.get_or("foo", "list3", || vec![0]).unwrap(),
vec![2, 3, 1]
);
assert_eq!(cfg.get_or_default::<bool>("foo", "bool1").unwrap(), true);
assert_eq!(
cfg.get_or_default::<Vec<bool>>("foo", "bools").unwrap(),
vec![true, true, true, true, false, false, false, false]
);
assert_eq!(
cfg.get_or_default::<ByteCount>("foo", "byte1")
.unwrap()
.value(),
1536
);
assert_eq!(
cfg.get_or_default::<ByteCount>("foo", "byte2")
.unwrap()
.value(),
500
);
assert_eq!(
cfg.get_or_default::<ByteCount>("foo", "byte3")
.unwrap()
.value(),
131072
);
assert_eq!(
cfg.get_or("foo", "missing", || ByteCount::from(3))
.unwrap()
.value(),
3
);
assert_eq!(cfg.get_or("foo", "float", || 42f32).unwrap(), 1.42f32);
}
}

View File

@ -0,0 +1,507 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
use std::path::PathBuf;
use anyhow::Result;
use minibytes::Text;
use util::path::expand_path;
use crate::error::Error;
pub trait FromConfigValue: Sized {
fn try_from_str(s: &str) -> Result<Self>;
}
impl FromConfigValue for bool {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.to_lowercase();
match value.as_ref() {
"1" | "yes" | "true" | "on" | "always" => Ok(true),
"0" | "no" | "false" | "off" | "never" => Ok(false),
_ => Err(Error::Convert(format!("invalid bool: {}", value)).into()),
}
}
}
impl FromConfigValue for i8 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for i16 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for i32 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for i64 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for isize {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u8 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u16 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u32 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u64 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for usize {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for f32 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for f64 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for String {
fn try_from_str(s: &str) -> Result<Self> {
Ok(s.to_string())
}
}
/// Byte count specified with a unit. For example: `1.5 MB`.
#[derive(Copy, Clone, Default)]
pub struct ByteCount(u64);
impl ByteCount {
/// Get the value of bytes. For example, `1K` has a value of `1024`.
pub fn value(self) -> u64 {
self.0
}
}
impl From<u64> for ByteCount {
fn from(value: u64) -> ByteCount {
ByteCount(value)
}
}
impl FromConfigValue for ByteCount {
fn try_from_str(s: &str) -> Result<Self> {
// This implementation matches mercurial/util.py:sizetoint
let sizeunits = [
("kb", 1u64 << 10),
("mb", 1 << 20),
("gb", 1 << 30),
("tb", 1 << 40),
("k", 1 << 10),
("m", 1 << 20),
("g", 1 << 30),
("t", 1 << 40),
("b", 1),
("", 1),
];
let value = s.to_lowercase();
for (suffix, unit) in sizeunits.iter() {
if value.ends_with(suffix) {
let number_str: &str = value[..value.len() - suffix.len()].trim();
let number: f64 = number_str.parse()?;
if number < 0.0 {
return Err(Error::Convert(format!(
"byte size '{:?}' cannot be negative",
value
))
.into());
}
let unit = *unit as f64;
return Ok(ByteCount((number * unit) as u64));
}
}
Err(Error::Convert(format!("'{:?}' cannot be parsed as a byte size", value)).into())
}
}
impl FromConfigValue for PathBuf {
fn try_from_str(s: &str) -> Result<Self> {
Ok(expand_path(s))
}
}
impl<T: FromConfigValue> FromConfigValue for Vec<T> {
fn try_from_str(s: &str) -> Result<Self> {
let items = parse_list(s);
items.into_iter().map(|s| T::try_from_str(&s)).collect()
}
}
impl<T: FromConfigValue> FromConfigValue for Option<T> {
fn try_from_str(s: &str) -> Result<Self> {
T::try_from_str(s).map(Option::Some)
}
}
/// Parse a configuration value as a list of comma/space separated strings.
/// It is ported from `mercurial.config.parselist`.
///
/// The function never complains about syntax and always returns some result.
///
/// Example:
///
/// ```
/// use configparser::convert::parse_list;
///
/// assert_eq!(
/// parse_list("this,is \"a small\" ,test"),
/// vec!["this".to_string(), "is".to_string(), "a small".to_string(), "test".to_string()]
/// );
/// ```
pub fn parse_list<B: AsRef<str>>(value: B) -> Vec<Text> {
let mut value = value.as_ref();
// ```python
// if value is not None and isinstance(value, bytes):
// result = _configlist(value.lstrip(' ,\n'))
// ```
while [" ", ",", "\n"].iter().any(|b| value.starts_with(b)) {
value = &value[1..]
}
parse_list_internal(value)
.into_iter()
.map(Text::from)
.collect()
}
fn parse_list_internal(value: &str) -> Vec<String> {
let mut value = value;
// ```python
// def _configlist(s):
// s = s.rstrip(' ,')
// if not s:
// return []
// parser, parts, offset = _parse_plain, [''], 0
// while parser:
// parser, parts, offset = parser(parts, s, offset)
// return parts
// ```
value = value.trim_end_matches(|c| " ,\n".contains(c));
if value.is_empty() {
return Vec::new();
}
#[derive(Copy, Clone)]
enum State {
Plain,
Quote,
}
let mut offset = 0;
let mut parts: Vec<String> = vec![String::new()];
let mut state = State::Plain;
let value: Vec<char> = value.chars().collect();
loop {
match state {
// ```python
// def _parse_plain(parts, s, offset):
// whitespace = False
// while offset < len(s) and (s[offset:offset + 1].isspace()
// or s[offset:offset + 1] == ','):
// whitespace = True
// offset += 1
// if offset >= len(s):
// return None, parts, offset
// if whitespace:
// parts.append('')
// if s[offset:offset + 1] == '"' and not parts[-1]:
// return _parse_quote, parts, offset + 1
// elif s[offset:offset + 1] == '"' and parts[-1][-1:] == '\\':
// parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
// return _parse_plain, parts, offset + 1
// parts[-1] += s[offset:offset + 1]
// return _parse_plain, parts, offset + 1
// ```
State::Plain => {
let mut whitespace = false;
while offset < value.len() && " \n\r\t,".contains(value[offset]) {
whitespace = true;
offset += 1;
}
if offset >= value.len() {
break;
}
if whitespace {
parts.push(Default::default());
}
if value[offset] == '"' {
let branch = {
match parts.last() {
None => 1,
Some(last) => {
if last.is_empty() {
1
} else if last.ends_with('\\') {
2
} else {
3
}
}
}
}; // manual NLL, to drop reference on "parts".
if branch == 1 {
// last.is_empty()
state = State::Quote;
offset += 1;
continue;
} else if branch == 2 {
// last.ends_with(b"\\")
let last = parts.last_mut().unwrap();
last.pop();
last.push(value[offset]);
offset += 1;
continue;
}
}
let last = parts.last_mut().unwrap();
last.push(value[offset]);
offset += 1;
}
// ```python
// def _parse_quote(parts, s, offset):
// if offset < len(s) and s[offset:offset + 1] == '"': # ""
// parts.append('')
// offset += 1
// while offset < len(s) and (s[offset:offset + 1].isspace() or
// s[offset:offset + 1] == ','):
// offset += 1
// return _parse_plain, parts, offset
// while offset < len(s) and s[offset:offset + 1] != '"':
// if (s[offset:offset + 1] == '\\' and offset + 1 < len(s)
// and s[offset + 1:offset + 2] == '"'):
// offset += 1
// parts[-1] += '"'
// else:
// parts[-1] += s[offset:offset + 1]
// offset += 1
// if offset >= len(s):
// real_parts = _configlist(parts[-1])
// if not real_parts:
// parts[-1] = '"'
// else:
// real_parts[0] = '"' + real_parts[0]
// parts = parts[:-1]
// parts.extend(real_parts)
// return None, parts, offset
// offset += 1
// while offset < len(s) and s[offset:offset + 1] in [' ', ',']:
// offset += 1
// if offset < len(s):
// if offset + 1 == len(s) and s[offset:offset + 1] == '"':
// parts[-1] += '"'
// offset += 1
// else:
// parts.append('')
// else:
// return None, parts, offset
// return _parse_plain, parts, offset
// ```
State::Quote => {
if offset < value.len() && value[offset] == '"' {
parts.push(Default::default());
offset += 1;
while offset < value.len() && " \n\r\t,".contains(value[offset]) {
offset += 1;
}
state = State::Plain;
continue;
}
while offset < value.len() && value[offset] != '"' {
if value[offset] == '\\' && offset + 1 < value.len() && value[offset + 1] == '"'
{
offset += 1;
parts.last_mut().unwrap().push('"');
} else {
parts.last_mut().unwrap().push(value[offset]);
}
offset += 1;
}
if offset >= value.len() {
let mut real_parts: Vec<String> = parse_list_internal(parts.last().unwrap());
if real_parts.is_empty() {
parts.pop();
parts.push("\"".to_string());
} else {
real_parts[0].insert(0, '"');
parts.pop();
parts.append(&mut real_parts);
}
break;
}
offset += 1;
while offset < value.len() && " ,".contains(value[offset]) {
offset += 1;
}
if offset < value.len() {
if offset + 1 == value.len() && value[offset] == '"' {
parts.last_mut().unwrap().push('"');
offset += 1;
} else {
parts.push(Default::default());
}
} else {
break;
}
state = State::Plain;
}
}
}
parts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_list() {
fn b<B: AsRef<str>>(bytes: B) -> Text {
Text::copy_from_slice(bytes.as_ref())
}
// From test-ui-config.py
assert_eq!(parse_list("foo"), vec![b("foo")]);
assert_eq!(
parse_list("foo bar baz"),
vec![b("foo"), b("bar"), b("baz")]
);
assert_eq!(parse_list("alice, bob"), vec![b("alice"), b("bob")]);
assert_eq!(
parse_list("foo bar baz alice, bob"),
vec![b("foo"), b("bar"), b("baz"), b("alice"), b("bob")]
);
assert_eq!(
parse_list("abc d\"ef\"g \"hij def\""),
vec![b("abc"), b("d\"ef\"g"), b("hij def")]
);
assert_eq!(
parse_list("\"hello world\", \"how are you?\""),
vec![b("hello world"), b("how are you?")]
);
assert_eq!(
parse_list("Do\"Not\"Separate"),
vec![b("Do\"Not\"Separate")]
);
assert_eq!(parse_list("\"Do\"Separate"), vec![b("Do"), b("Separate")]);
assert_eq!(
parse_list("\"Do\\\"NotSeparate\""),
vec![b("Do\"NotSeparate")]
);
assert_eq!(
parse_list("string \"with extraneous\" quotation mark\""),
vec![
b("string"),
b("with extraneous"),
b("quotation"),
b("mark\""),
]
);
assert_eq!(parse_list("x, y"), vec![b("x"), b("y")]);
assert_eq!(parse_list("\"x\", \"y\""), vec![b("x"), b("y")]);
assert_eq!(
parse_list("\"\"\" key = \"x\", \"y\" \"\"\""),
vec![b(""), b(" key = "), b("x\""), b("y"), b(""), b("\"")]
);
assert_eq!(parse_list(",,,, "), Vec::<Text>::new());
assert_eq!(
parse_list("\" just with starting quotation"),
vec![b("\""), b("just"), b("with"), b("starting"), b("quotation")]
);
assert_eq!(
parse_list("\"longer quotation\" with \"no ending quotation"),
vec![
b("longer quotation"),
b("with"),
b("\"no"),
b("ending"),
b("quotation"),
]
);
assert_eq!(
parse_list("this is \\\" \"not a quotation mark\""),
vec![b("this"), b("is"), b("\""), b("not a quotation mark")]
);
assert_eq!(parse_list("\n \n\nding\ndong"), vec![b("ding"), b("dong")]);
// 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\" d"), vec![b("a"), b("b,c"), b("d")]);
assert_eq!(parse_list("a,\",c"), vec![b("a"), b("\""), b("c")]);
assert_eq!(parse_list("a,\" c\" \""), vec![b("a"), b(" c\"")]);
assert_eq!(
parse_list("a,\" c\" \" d"),
vec![b("a"), b(" c"), b("\""), b("d")]
);
}
}

View File

@ -77,32 +77,6 @@ pub trait ConfigSetHgExt {
/// Load a specified config file. Respect HGPLAIN environment variables.
/// Return errors parsing files.
fn load_hgrc(&mut self, path: impl AsRef<Path>, source: &'static str) -> Vec<Error>;
/// Get a config item. Convert to type `T`.
fn get_opt<T: FromConfigValue>(&self, section: &str, name: &str) -> Result<Option<T>>;
/// Get a config item. Convert to type `T`.
///
/// If the config item is not set, calculate it using `default_func`.
fn get_or<T: FromConfigValue>(
&self,
section: &str,
name: &str,
default_func: impl Fn() -> T,
) -> Result<T> {
Ok(self.get_opt(section, name)?.unwrap_or_else(default_func))
}
/// Get a config item. Convert to type `T`.
///
/// If the config item is not set, return `T::default()`.
fn get_or_default<T: Default + FromConfigValue>(&self, section: &str, name: &str) -> Result<T> {
self.get_or(section, name, Default::default)
}
}
pub trait FromConfigValue: Sized {
fn try_from_str(s: &str) -> Result<Self>;
}
pub fn load<S: Into<Text>, N: Into<Text>>(
@ -445,12 +419,6 @@ impl ConfigSetHgExt for ConfigSet {
let opts = Options::new().source(source).process_hgplain();
self.load_path(path, &opts)
}
fn get_opt<T: FromConfigValue>(&self, section: &str, name: &str) -> Result<Option<T>> {
ConfigSet::get(self, section, name)
.map(|bytes| T::try_from_str(&bytes))
.transpose()
}
}
impl ConfigSet {
@ -514,404 +482,6 @@ impl ConfigSet {
}
}
impl FromConfigValue for bool {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.to_lowercase();
match value.as_ref() {
"1" | "yes" | "true" | "on" | "always" => Ok(true),
"0" | "no" | "false" | "off" | "never" => Ok(false),
_ => Err(Error::Convert(format!("invalid bool: {}", value)).into()),
}
}
}
impl FromConfigValue for i8 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for i16 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for i32 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for i64 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for isize {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u8 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u16 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u32 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for u64 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for usize {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for f32 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for f64 {
fn try_from_str(s: &str) -> Result<Self> {
let value = s.parse()?;
Ok(value)
}
}
impl FromConfigValue for String {
fn try_from_str(s: &str) -> Result<Self> {
Ok(s.to_string())
}
}
/// Byte count specified with a unit. For example: `1.5 MB`.
#[derive(Copy, Clone, Default)]
pub struct ByteCount(u64);
impl ByteCount {
/// Get the value of bytes. For example, `1K` has a value of `1024`.
pub fn value(self) -> u64 {
self.0
}
}
impl From<u64> for ByteCount {
fn from(value: u64) -> ByteCount {
ByteCount(value)
}
}
impl FromConfigValue for ByteCount {
fn try_from_str(s: &str) -> Result<Self> {
// This implementation matches mercurial/util.py:sizetoint
let sizeunits = [
("kb", 1u64 << 10),
("mb", 1 << 20),
("gb", 1 << 30),
("tb", 1 << 40),
("k", 1 << 10),
("m", 1 << 20),
("g", 1 << 30),
("t", 1 << 40),
("b", 1),
("", 1),
];
let value = s.to_lowercase();
for (suffix, unit) in sizeunits.iter() {
if value.ends_with(suffix) {
let number_str: &str = value[..value.len() - suffix.len()].trim();
let number: f64 = number_str.parse()?;
if number < 0.0 {
return Err(Error::Convert(format!(
"byte size '{:?}' cannot be negative",
value
))
.into());
}
let unit = *unit as f64;
return Ok(ByteCount((number * unit) as u64));
}
}
Err(Error::Convert(format!("'{:?}' cannot be parsed as a byte size", value)).into())
}
}
impl FromConfigValue for PathBuf {
fn try_from_str(s: &str) -> Result<Self> {
Ok(expand_path(s))
}
}
impl<T: FromConfigValue> FromConfigValue for Vec<T> {
fn try_from_str(s: &str) -> Result<Self> {
let items = parse_list(s);
items.into_iter().map(|s| T::try_from_str(&s)).collect()
}
}
impl<T: FromConfigValue> FromConfigValue for Option<T> {
fn try_from_str(s: &str) -> Result<Self> {
T::try_from_str(s).map(Option::Some)
}
}
/// Parse a configuration value as a list of comma/space separated strings.
/// It is ported from `mercurial.config.parselist`.
///
/// The function never complains about syntax and always returns some result.
///
/// Example:
///
/// ```
/// use configparser::hg::parse_list;
///
/// assert_eq!(
/// parse_list("this,is \"a small\" ,test"),
/// vec!["this".to_string(), "is".to_string(), "a small".to_string(), "test".to_string()]
/// );
/// ```
pub fn parse_list<B: AsRef<str>>(value: B) -> Vec<Text> {
let mut value = value.as_ref();
// ```python
// if value is not None and isinstance(value, bytes):
// result = _configlist(value.lstrip(' ,\n'))
// ```
while [" ", ",", "\n"].iter().any(|b| value.starts_with(b)) {
value = &value[1..]
}
parse_list_internal(value)
.into_iter()
.map(Text::from)
.collect()
}
fn parse_list_internal(value: &str) -> Vec<String> {
let mut value = value;
// ```python
// def _configlist(s):
// s = s.rstrip(' ,')
// if not s:
// return []
// parser, parts, offset = _parse_plain, [''], 0
// while parser:
// parser, parts, offset = parser(parts, s, offset)
// return parts
// ```
value = value.trim_end_matches(|c| " ,\n".contains(c));
if value.is_empty() {
return Vec::new();
}
#[derive(Copy, Clone)]
enum State {
Plain,
Quote,
}
let mut offset = 0;
let mut parts: Vec<String> = vec![String::new()];
let mut state = State::Plain;
let value: Vec<char> = value.chars().collect();
loop {
match state {
// ```python
// def _parse_plain(parts, s, offset):
// whitespace = False
// while offset < len(s) and (s[offset:offset + 1].isspace()
// or s[offset:offset + 1] == ','):
// whitespace = True
// offset += 1
// if offset >= len(s):
// return None, parts, offset
// if whitespace:
// parts.append('')
// if s[offset:offset + 1] == '"' and not parts[-1]:
// return _parse_quote, parts, offset + 1
// elif s[offset:offset + 1] == '"' and parts[-1][-1:] == '\\':
// parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
// return _parse_plain, parts, offset + 1
// parts[-1] += s[offset:offset + 1]
// return _parse_plain, parts, offset + 1
// ```
State::Plain => {
let mut whitespace = false;
while offset < value.len() && " \n\r\t,".contains(value[offset]) {
whitespace = true;
offset += 1;
}
if offset >= value.len() {
break;
}
if whitespace {
parts.push(Default::default());
}
if value[offset] == '"' {
let branch = {
match parts.last() {
None => 1,
Some(last) => {
if last.is_empty() {
1
} else if last.ends_with('\\') {
2
} else {
3
}
}
}
}; // manual NLL, to drop reference on "parts".
if branch == 1 {
// last.is_empty()
state = State::Quote;
offset += 1;
continue;
} else if branch == 2 {
// last.ends_with(b"\\")
let last = parts.last_mut().unwrap();
last.pop();
last.push(value[offset]);
offset += 1;
continue;
}
}
let last = parts.last_mut().unwrap();
last.push(value[offset]);
offset += 1;
}
// ```python
// def _parse_quote(parts, s, offset):
// if offset < len(s) and s[offset:offset + 1] == '"': # ""
// parts.append('')
// offset += 1
// while offset < len(s) and (s[offset:offset + 1].isspace() or
// s[offset:offset + 1] == ','):
// offset += 1
// return _parse_plain, parts, offset
// while offset < len(s) and s[offset:offset + 1] != '"':
// if (s[offset:offset + 1] == '\\' and offset + 1 < len(s)
// and s[offset + 1:offset + 2] == '"'):
// offset += 1
// parts[-1] += '"'
// else:
// parts[-1] += s[offset:offset + 1]
// offset += 1
// if offset >= len(s):
// real_parts = _configlist(parts[-1])
// if not real_parts:
// parts[-1] = '"'
// else:
// real_parts[0] = '"' + real_parts[0]
// parts = parts[:-1]
// parts.extend(real_parts)
// return None, parts, offset
// offset += 1
// while offset < len(s) and s[offset:offset + 1] in [' ', ',']:
// offset += 1
// if offset < len(s):
// if offset + 1 == len(s) and s[offset:offset + 1] == '"':
// parts[-1] += '"'
// offset += 1
// else:
// parts.append('')
// else:
// return None, parts, offset
// return _parse_plain, parts, offset
// ```
State::Quote => {
if offset < value.len() && value[offset] == '"' {
parts.push(Default::default());
offset += 1;
while offset < value.len() && " \n\r\t,".contains(value[offset]) {
offset += 1;
}
state = State::Plain;
continue;
}
while offset < value.len() && value[offset] != '"' {
if value[offset] == '\\' && offset + 1 < value.len() && value[offset + 1] == '"'
{
offset += 1;
parts.last_mut().unwrap().push('"');
} else {
parts.last_mut().unwrap().push(value[offset]);
}
offset += 1;
}
if offset >= value.len() {
let mut real_parts: Vec<String> = parse_list_internal(parts.last().unwrap());
if real_parts.is_empty() {
parts.pop();
parts.push("\"".to_string());
} else {
real_parts[0].insert(0, '"');
parts.pop();
parts.append(&mut real_parts);
}
break;
}
offset += 1;
while offset < value.len() && " ,".contains(value[offset]) {
offset += 1;
}
if offset < value.len() {
if offset + 1 == value.len() && value[offset] == '"' {
parts.last_mut().unwrap().push('"');
offset += 1;
} else {
parts.push(Default::default());
}
} else {
break;
}
state = State::Plain;
}
}
}
parts
}
fn get_shared_path(repo_path: &Path) -> Result<PathBuf> {
let shared_path = repo_path.join("sharedpath");
Ok(if shared_path.exists() {
@ -1187,161 +757,6 @@ mod tests {
assert_eq!(cfg.get("y", "b"), None);
assert_eq!(cfg.get("z", "c"), Some("3".into()));
}
#[test]
fn test_parse_list() {
fn b<B: AsRef<str>>(bytes: B) -> Text {
Text::copy_from_slice(bytes.as_ref())
}
// From test-ui-config.py
assert_eq!(parse_list("foo"), vec![b("foo")]);
assert_eq!(
parse_list("foo bar baz"),
vec![b("foo"), b("bar"), b("baz")]
);
assert_eq!(parse_list("alice, bob"), vec![b("alice"), b("bob")]);
assert_eq!(
parse_list("foo bar baz alice, bob"),
vec![b("foo"), b("bar"), b("baz"), b("alice"), b("bob")]
);
assert_eq!(
parse_list("abc d\"ef\"g \"hij def\""),
vec![b("abc"), b("d\"ef\"g"), b("hij def")]
);
assert_eq!(
parse_list("\"hello world\", \"how are you?\""),
vec![b("hello world"), b("how are you?")]
);
assert_eq!(
parse_list("Do\"Not\"Separate"),
vec![b("Do\"Not\"Separate")]
);
assert_eq!(parse_list("\"Do\"Separate"), vec![b("Do"), b("Separate")]);
assert_eq!(
parse_list("\"Do\\\"NotSeparate\""),
vec![b("Do\"NotSeparate")]
);
assert_eq!(
parse_list("string \"with extraneous\" quotation mark\""),
vec![
b("string"),
b("with extraneous"),
b("quotation"),
b("mark\""),
]
);
assert_eq!(parse_list("x, y"), vec![b("x"), b("y")]);
assert_eq!(parse_list("\"x\", \"y\""), vec![b("x"), b("y")]);
assert_eq!(
parse_list("\"\"\" key = \"x\", \"y\" \"\"\""),
vec![b(""), b(" key = "), b("x\""), b("y"), b(""), b("\"")]
);
assert_eq!(parse_list(",,,, "), Vec::<Text>::new());
assert_eq!(
parse_list("\" just with starting quotation"),
vec![b("\""), b("just"), b("with"), b("starting"), b("quotation")]
);
assert_eq!(
parse_list("\"longer quotation\" with \"no ending quotation"),
vec![
b("longer quotation"),
b("with"),
b("\"no"),
b("ending"),
b("quotation"),
]
);
assert_eq!(
parse_list("this is \\\" \"not a quotation mark\""),
vec![b("this"), b("is"), b("\""), b("not a quotation mark")]
);
assert_eq!(parse_list("\n \n\nding\ndong"), vec![b("ding"), b("dong")]);
// 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\" d"), vec![b("a"), b("b,c"), b("d")]);
assert_eq!(parse_list("a,\",c"), vec![b("a"), b("\""), b("c")]);
assert_eq!(parse_list("a,\" c\" \""), vec![b("a"), b(" c\"")]);
assert_eq!(
parse_list("a,\" c\" \" d"),
vec![b("a"), b(" c"), b("\""), b("d")]
);
}
#[test]
fn test_get_or() {
let mut cfg = ConfigSet::new();
cfg.parse(
"[foo]\n\
bool1 = yes\n\
bool2 = unknown\n\
bools = 1, TRUE, On, aLwAys, 0, false, oFF, never\n\
int1 = -33\n\
list1 = x y z\n\
list3 = 2, 3, 1\n\
byte1 = 1.5 KB\n\
byte2 = 500\n\
byte3 = 0.125M\n\
float = 1.42\n\
",
&"test".into(),
);
assert_eq!(cfg.get_or("foo", "bar", || 3).unwrap(), 3);
assert_eq!(cfg.get_or("foo", "bool1", || false).unwrap(), true);
assert_eq!(
format!("{}", cfg.get_or("foo", "bool2", || true).unwrap_err()),
"invalid bool: unknown"
);
assert_eq!(cfg.get_or("foo", "int1", || 42).unwrap(), -33);
assert_eq!(
cfg.get_or("foo", "list1", || vec!["x".to_string()])
.unwrap(),
vec!["x", "y", "z"]
);
assert_eq!(
cfg.get_or("foo", "list3", || vec![0]).unwrap(),
vec![2, 3, 1]
);
assert_eq!(cfg.get_or_default::<bool>("foo", "bool1").unwrap(), true);
assert_eq!(
cfg.get_or_default::<Vec<bool>>("foo", "bools").unwrap(),
vec![true, true, true, true, false, false, false, false]
);
assert_eq!(
cfg.get_or_default::<ByteCount>("foo", "byte1")
.unwrap()
.value(),
1536
);
assert_eq!(
cfg.get_or_default::<ByteCount>("foo", "byte2")
.unwrap()
.value(),
500
);
assert_eq!(
cfg.get_or_default::<ByteCount>("foo", "byte3")
.unwrap()
.value(),
131072
);
assert_eq!(
cfg.get_or("foo", "missing", || ByteCount::from(3))
.unwrap()
.value(),
3
);
assert_eq!(cfg.get_or("foo", "float", || 42f32).unwrap(), 1.42f32);
}
}
const MERGE_TOOLS_CONFIG: &str = r#"# Some default global settings for common merge tools

View File

@ -68,6 +68,7 @@
pub mod c_api;
pub mod config;
pub mod convert;
pub mod dynamicconfig;
pub mod error;
pub mod hg;

View File

@ -15,7 +15,7 @@ use url::Url;
use anyhow::anyhow;
use auth::AuthConfig;
use configparser::{config::ConfigSet, hg::ConfigSetHgExt};
use configparser::config::ConfigSet;
use http_client::HttpVersion;
use crate::client::Client;

View File

@ -17,10 +17,7 @@ use minibytes::Bytes;
use regex::Regex;
use tracing::info_span;
use configparser::{
config::ConfigSet,
hg::{ByteCount, ConfigSetHgExt},
};
use configparser::{config::ConfigSet, convert::ByteCount};
use hgtime::HgTime;
use types::{Key, RepoPathBuf};

View File

@ -20,10 +20,7 @@ use minibytes::Bytes;
use parking_lot::RwLock;
use tokio::task::spawn_blocking;
use configparser::{
config::ConfigSet,
hg::{ByteCount, ConfigSetHgExt},
};
use configparser::{config::ConfigSet, convert::ByteCount};
use edenapi_types::TreeEntry;
use indexedlog::log::IndexOutput;
use lz4_pyframe::{compress, decompress};

View File

@ -15,10 +15,7 @@ use anyhow::Result;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha1::{Digest, Sha1};
use configparser::{
config::ConfigSet,
hg::{ByteCount, ConfigSetHgExt},
};
use configparser::{config::ConfigSet, convert::ByteCount};
use indexedlog::log::IndexOutput;
use types::{
hgid::{ReadHgIdExt, WriteHgIdExt},

View File

@ -39,10 +39,7 @@ use url::Url;
use async_runtime::block_on_exclusive as block_on_future;
use auth::{Auth, AuthConfig};
use configparser::{
config::ConfigSet,
hg::{ByteCount, ConfigSetHgExt},
};
use configparser::{config::ConfigSet, convert::ByteCount};
use hg_http::http_client;
use http_client::{HttpClient, HttpClientError, HttpVersion, Method, MinTransferSpeed, Request};
use indexedlog::{log::IndexOutput, rotate, DefaultOpenOptions, Repair};

View File

@ -12,10 +12,7 @@ use std::{
use anyhow::{format_err, Result};
use configparser::{
config::ConfigSet,
hg::{ByteCount, ConfigSetHgExt},
};
use configparser::{config::ConfigSet, convert::ByteCount};
use types::{Key, NodeInfo};
use crate::{

View File

@ -14,7 +14,7 @@ use std::{
};
use anyhow::{format_err, Error, Result};
use configparser::{config::ConfigSet, hg::ByteCount, hg::ConfigSetHgExt};
use configparser::{config::ConfigSet, convert::ByteCount};
use minibytes::Bytes;
use thiserror::Error;

View File

@ -13,7 +13,7 @@ use anyhow::Result;
use hgtime::HgTime;
use thiserror::Error;
use configparser::{config::ConfigSet, hg::ConfigSetHgExt};
use configparser::config::ConfigSet;
use util::path::{create_dir, create_shared_dir};
#[derive(Error, Debug)]