mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
option_parser: Split Tuple into Tuple and TupleList
We split the existing `Tuple` type into two types, one that represents a single `Tuple` and one that represents a list of tuples. This allows us to test tuple parsing and the parsing of tuple lists via distinct unit tests in follow-up commits. The renaming also brings consistency by adapting the naming scheme of the other list types defined in `option_parser`, e.g. `IntegerList` and `StringList`. Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de> On-behalf-of: SAP pascal.scholz@sap.com
This commit is contained in:
committed by
Rob Bradford
parent
fee153841b
commit
8b6f4b0892
@@ -272,7 +272,7 @@ impl OptionParser {
|
||||
///
|
||||
/// `T` can be any type that implements `FromStr` (e.g. `u32`, `String`),
|
||||
/// or one of this crate's types such as [`Toggle`], [`IntegerList`],
|
||||
/// [`Tuple`], or [`StringList`].
|
||||
/// [`Tuple`], [`TupleList`] or [`StringList`].
|
||||
pub fn convert<T: Parseable>(&self, option: &str) -> OptionParserResult<Option<T>> {
|
||||
match self.options.get(option).and_then(|v| v.value.as_ref()) {
|
||||
None => Ok(None),
|
||||
@@ -464,62 +464,76 @@ impl TupleValue for Vec<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of `key@value` pairs parsed from a bracket-enclosed string.
|
||||
///
|
||||
/// The format is `[key1@value1,key2@value2,...]` where `@` separates each
|
||||
/// pair's elements. `S` is the key type and `T` is the value type.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Tuple<S, T>(pub Vec<(S, T)>);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TupleError {
|
||||
#[error("invalid value: {0}")]
|
||||
InvalidValue(String),
|
||||
#[error("split outside brackets")]
|
||||
SplitOutsideBrackets(#[source] OptionParserError),
|
||||
#[error("Unbalanced brackets in one of the values")]
|
||||
SplitInsideBrackets(#[source] OptionParserError),
|
||||
#[error("Expected a single pair of enclosing brackets in input: {0}")]
|
||||
UnbalancedOutsideBrackets(String),
|
||||
#[error("invalid integer list")]
|
||||
InvalidIntegerList(#[source] IntegerListParseError),
|
||||
#[error("invalid integer")]
|
||||
InvalidInteger(#[source] ParseIntError),
|
||||
}
|
||||
|
||||
/// A tuple consisting of a `key@value` pair parsed from a string.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Tuple<S, T>(pub S, pub T);
|
||||
|
||||
/// A list of `key@value` pairs parsed from a bracket-enclosed string.
|
||||
///
|
||||
/// The format is `[key1@value1,key2@value2,...]` where `@` separates each
|
||||
/// pair's elements. `S` is the key type and `T` is the value type.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct TupleList<S, T>(pub Vec<Tuple<S, T>>);
|
||||
|
||||
impl<S: Parseable, T: TupleValue> Parseable for Tuple<S, T> {
|
||||
type Err = TupleError;
|
||||
|
||||
fn from_str(tuple: &str) -> result::Result<Self, Self::Err> {
|
||||
let mut in_quotes = false;
|
||||
let mut last_idx = 0;
|
||||
let mut first_val = None;
|
||||
for (idx, c) in tuple.as_bytes().iter().enumerate() {
|
||||
match c {
|
||||
b'"' => in_quotes = !in_quotes,
|
||||
b'@' if !in_quotes => {
|
||||
if last_idx != 0 {
|
||||
return Err(TupleError::InvalidValue((*tuple).to_string()));
|
||||
}
|
||||
first_val = Some(&tuple[last_idx..idx]);
|
||||
last_idx = idx + 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let item1 = <S as Parseable>::from_str(
|
||||
first_val.ok_or(TupleError::InvalidValue((*tuple).to_string()))?,
|
||||
)
|
||||
.map_err(|_| TupleError::InvalidValue(first_val.unwrap().to_owned()))?;
|
||||
let item2: T = TupleValue::parse_value(&tuple[last_idx..])?;
|
||||
Ok(Tuple(item1, item2))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Parseable, T: TupleValue> Parseable for TupleList<S, T> {
|
||||
type Err = TupleError;
|
||||
|
||||
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
|
||||
let mut list: Vec<(S, T)> = Vec::new();
|
||||
let mut list: Vec<Tuple<S, T>> = Vec::new();
|
||||
let body = s
|
||||
.trim()
|
||||
.strip_prefix('[')
|
||||
.and_then(|s| s.strip_suffix(']'))
|
||||
.ok_or_else(|| TupleError::InvalidValue(s.to_string()))?;
|
||||
let tuples_list = split_commas(body).map_err(TupleError::SplitOutsideBrackets)?;
|
||||
for tuple in tuples_list.iter() {
|
||||
let mut in_quotes = false;
|
||||
let mut last_idx = 0;
|
||||
let mut first_val = None;
|
||||
for (idx, c) in tuple.as_bytes().iter().enumerate() {
|
||||
match c {
|
||||
b'"' => in_quotes = !in_quotes,
|
||||
b'@' if !in_quotes => {
|
||||
if last_idx != 0 {
|
||||
return Err(TupleError::InvalidValue((*tuple).to_string()));
|
||||
}
|
||||
first_val = Some(&tuple[last_idx..idx]);
|
||||
last_idx = idx + 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let item1 = <S as Parseable>::from_str(
|
||||
first_val.ok_or(TupleError::InvalidValue((*tuple).to_string()))?,
|
||||
)
|
||||
.map_err(|_| TupleError::InvalidValue(first_val.unwrap().to_owned()))?;
|
||||
let item2 = TupleValue::parse_value(&tuple[last_idx..])?;
|
||||
list.push((item1, item2));
|
||||
.ok_or_else(|| TupleError::UnbalancedOutsideBrackets(s.to_string()))?;
|
||||
let tuples_raw = split_commas(body).map_err(TupleError::SplitInsideBrackets)?;
|
||||
for tuple_raw in tuples_raw.iter() {
|
||||
list.push(Tuple::from_str(tuple_raw)?);
|
||||
}
|
||||
|
||||
Ok(Tuple(list))
|
||||
Ok(TupleList(list))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,10 +650,10 @@ mod unit_tests {
|
||||
parser.parse("topology=[\"@\"\"b\"@[1,2]]").unwrap();
|
||||
assert_eq!(
|
||||
parser
|
||||
.convert::<Tuple<String, Vec<u8>>>("topology")
|
||||
.convert::<TupleList<String, Vec<u8>>>("topology")
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
Tuple(vec![("@\"b".to_owned(), vec![1, 2])])
|
||||
TupleList(vec![Tuple("@\"b".to_owned(), vec![1, 2])])
|
||||
);
|
||||
|
||||
parser.parse("cmdline=\"console=ttyS0,9600n8\"").unwrap();
|
||||
@@ -836,30 +850,27 @@ mod unit_tests {
|
||||
|
||||
#[test]
|
||||
fn test_tuple_single_pair() {
|
||||
let t = Tuple::<String, u64>::from_str("[foo@42]").unwrap();
|
||||
assert_eq!(t, Tuple(vec![("foo".to_owned(), 42)]));
|
||||
let t = Tuple::<String, u64>::from_str("foo@42").unwrap();
|
||||
assert_eq!(t, Tuple("foo".to_owned(), 42));
|
||||
let t = Tuple::<String, Vec<u64>>::from_str("foo@[42]").unwrap();
|
||||
assert_eq!(t, Tuple("foo".to_owned(), vec![42]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_multiple_pairs() {
|
||||
let t = Tuple::<String, Vec<u64>>::from_str("[a@[1,2],b@[3,4]]").unwrap();
|
||||
fn test_tuple_list_multiple_pairs() {
|
||||
let t = TupleList::<String, Vec<u64>>::from_str("[a@[1,2],b@[3,4]]").unwrap();
|
||||
assert_eq!(
|
||||
t,
|
||||
Tuple(vec![
|
||||
("a".to_owned(), vec![1, 2]),
|
||||
("b".to_owned(), vec![3, 4]),
|
||||
TupleList(vec![
|
||||
Tuple("a".to_owned(), vec![1, 2]),
|
||||
Tuple("b".to_owned(), vec![3, 4]),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_missing_at_separator() {
|
||||
Tuple::<String, u64>::from_str("[foo42]").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_missing_brackets() {
|
||||
Tuple::<String, u64>::from_str("foo@42").unwrap_err();
|
||||
Tuple::<String, u64>::from_str("foo42").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -16,7 +16,7 @@ use block::ImageType;
|
||||
use clap::ArgMatches;
|
||||
use log::{debug, warn};
|
||||
use option_parser::{
|
||||
ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple,
|
||||
ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple, TupleList,
|
||||
};
|
||||
use pci::NUM_DEVICE_IDS;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -744,11 +744,11 @@ impl CpusConfig {
|
||||
.map_err(Error::ParseCpus)?
|
||||
.unwrap_or(DEFAULT_MAX_PHYS_BITS);
|
||||
let affinity = parser
|
||||
.convert::<Tuple<u32, Vec<usize>>>("affinity")
|
||||
.convert::<TupleList<u32, Vec<usize>>>("affinity")
|
||||
.map_err(Error::ParseCpus)?
|
||||
.map(|v| {
|
||||
v.0.iter()
|
||||
.map(|(e1, e2)| CpuAffinity {
|
||||
.map(|Tuple(e1, e2)| CpuAffinity {
|
||||
vcpu: *e1,
|
||||
host_cpus: e2.clone().into_boxed_slice(),
|
||||
})
|
||||
@@ -1529,11 +1529,11 @@ impl DiskConfig {
|
||||
.unwrap_or_default();
|
||||
let serial = parser.get("serial");
|
||||
let queue_affinity = parser
|
||||
.convert::<Tuple<u16, Vec<usize>>>("queue_affinity")
|
||||
.convert::<TupleList<u16, Vec<usize>>>("queue_affinity")
|
||||
.map_err(Error::ParseDisk)?
|
||||
.map(|v| {
|
||||
v.0.iter()
|
||||
.map(|(e1, e2)| VirtQueueAffinity {
|
||||
.map(|Tuple(e1, e2)| VirtQueueAffinity {
|
||||
queue_index: *e1,
|
||||
host_cpus: e2.clone().into_boxed_slice(),
|
||||
})
|
||||
@@ -2654,11 +2654,11 @@ impl NumaConfig {
|
||||
.map_err(Error::ParseNuma)?
|
||||
.map(|v| v.0.iter().map(|e| *e as u32).collect());
|
||||
let distances = parser
|
||||
.convert::<Tuple<u64, u64>>("distances")
|
||||
.convert::<TupleList<u64, u64>>("distances")
|
||||
.map_err(Error::ParseNuma)?
|
||||
.map(|v| {
|
||||
v.0.iter()
|
||||
.map(|(e1, e2)| NumaDistance {
|
||||
.map(|Tuple(e1, e2)| NumaDistance {
|
||||
destination: *e1 as u32,
|
||||
distance: *e2 as u8,
|
||||
})
|
||||
@@ -2850,11 +2850,11 @@ impl RestoreConfig {
|
||||
.map_err(Error::ParseRestore)?
|
||||
.unwrap_or_default();
|
||||
let net_fds = parser
|
||||
.convert::<Tuple<String, Vec<u64>>>("net_fds")
|
||||
.convert::<TupleList<String, Vec<u64>>>("net_fds")
|
||||
.map_err(Error::ParseRestore)?
|
||||
.map(|v| {
|
||||
v.0.iter()
|
||||
.map(|(id, fds)| RestoredNetConfig {
|
||||
.map(|Tuple(id, fds)| RestoredNetConfig {
|
||||
id: id.clone(),
|
||||
num_fds: fds.len(),
|
||||
fds: Some(fds.iter().map(|e| *e as i32).collect()),
|
||||
|
||||
Reference in New Issue
Block a user