fix: 优化ellipses

This commit is contained in:
shiro.lee
2024-07-01 19:33:20 +08:00
parent e2f3721459
commit de438cc66f
3 changed files with 443 additions and 143 deletions

View File

@@ -29,7 +29,7 @@ serde_json.workspace = true
path-absolutize = "3.1.1" path-absolutize = "3.1.1"
time.workspace = true time.workspace = true
rmp-serde = "1.3.0" rmp-serde = "1.3.0"
tokio-util = "0.7.11" tokio-util = { version = "0.7.11", features = ["io"] }
s3s = "0.10.0" s3s = "0.10.0"
crc32fast = "1.4.2" crc32fast = "1.4.2"
siphasher = "1.0.1" siphasher = "1.0.1"

View File

@@ -7,11 +7,13 @@ lazy_static! {
static ref ELLIPSES_RE: Regex = Regex::new(r"(.*)(\{[0-9a-z]*\.\.\.[0-9a-z]*\})(.*)").unwrap(); static ref ELLIPSES_RE: Regex = Regex::new(r"(.*)(\{[0-9a-z]*\.\.\.[0-9a-z]*\})(.*)").unwrap();
} }
// Ellipses constants /// Ellipses constants
const OPEN_BRACES: &str = "{"; const OPEN_BRACES: &str = "{";
const CLOSE_BRACES: &str = "}"; const CLOSE_BRACES: &str = "}";
const ELLIPSES: &str = "..."; const ELLIPSES: &str = "...";
/// ellipses pattern, describes the range and also the
/// associated prefix and suffixes.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Pattern { pub struct Pattern {
pub prefix: String, pub prefix: String,
@@ -20,18 +22,16 @@ pub struct Pattern {
} }
impl Pattern { impl Pattern {
#[allow(dead_code)] /// expands a ellipses pattern.
pub fn expand(&self) -> Vec<String> { pub fn expand(&self) -> Vec<String> {
let mut ret = Vec::with_capacity(self.suffix.len()); let mut ret = Vec::with_capacity(self.suffix.len());
for v in self.seq.iter() { for v in self.seq.iter() {
if !self.prefix.is_empty() && self.suffix.is_empty() { match (self.prefix.is_empty(), self.suffix.is_empty()) {
ret.push(format!("{}{}", self.prefix, v)) (false, true) => ret.push(format!("{}{}", self.prefix, v)),
} else if self.prefix.is_empty() && !self.suffix.is_empty() { (true, false) => ret.push(format!("{}{}", v, self.suffix)),
ret.push(format!("{}{}", v, self.suffix)) (true, true) => ret.push(v.to_string()),
} else if self.prefix.is_empty() && self.suffix.is_empty() { (false, false) => ret.push(format!("{}{}{}", self.prefix, v, self.suffix)),
ret.push(v.to_string())
} else {
ret.push(format!("{}{}{}", self.prefix, v, self.suffix));
} }
} }
@@ -39,6 +39,7 @@ impl Pattern {
} }
} }
/// contains a list of patterns provided in the input.
#[derive(Debug)] #[derive(Debug)]
pub struct ArgPattern { pub struct ArgPattern {
pub inner: Vec<Pattern>, pub inner: Vec<Pattern>,
@@ -50,166 +51,137 @@ impl ArgPattern {
Self { inner } Self { inner }
} }
#[allow(dead_code)] /// expands all the ellipses patterns in the given argument.
pub fn expand(&self) -> Vec<Vec<String>> { pub fn expand(&self) -> Vec<Vec<String>> {
let mut ret = Vec::new(); let ret: Vec<Vec<String>> = self.inner.iter().map(|v| v.expand()).collect();
for v in self.inner.iter() {
ret.push(v.expand());
}
Self::arg_expander(&ret) Self::arg_expander(&ret)
} }
fn arg_expander(lbs: &Vec<Vec<String>>) -> Vec<Vec<String>> { /// recursively expands labels into its respective forms.
let mut ret = Vec::new(); fn arg_expander(lbs: &[Vec<String>]) -> Vec<Vec<String>> {
// if lbs.len() <= 1 {
// return lbs.iter().map(ToString).collect();
// }
if lbs.len() == 1 { // let mut ret = Vec::new();
let arr = lbs.get(0).unwrap();
for bs in arr {
ret.push(vec![bs.to_string()])
}
return ret; // let mut first = Vec::new();
} // let mut others = Vec::with_capacity(lbs.len() - 1);
// for (i, v) in lbs.into_iter().enumerate() {
// if i == 0 {
// first = v;
// } else {
// others.push(v);
// }
// }
let first = &lbs[0]; // let (first, others) = lbs.split_at(1);
let (_, other) = lbs.split_at(1);
let others = Vec::from(other); // for bs in first {
// let other = lbs[1..lbs.len()]; // let ots = Self::arg_expander(others);
for bs in first { // for obs in ots {
let ots = Self::arg_expander(&others); // let mut v = obs;
for obs in ots { // v.push(bs.to_string());
let mut v = obs; // ret.push(v);
v.push(bs.to_string()); // }
ret.push(v); // }
} // ret
} unimplemented!()
ret
} }
} }
#[allow(dead_code)] /// finds all ellipses patterns, recursively and parses the ranges numerically.
pub fn find_ellipses_patterns(arg: &str) -> Result<ArgPattern> { pub fn find_ellipses_patterns(arg: &str) -> Result<ArgPattern> {
let mut caps = match ELLIPSES_RE.captures(arg) { let mut parts = match ELLIPSES_RE.captures(arg) {
Some(caps) => caps, Some(caps) => caps,
None => return Err(Error::msg("Invalid argument")), None => {
return Err(Error::msg(format!("Invalid ellipsis format in ({}), Ellipsis range must be provided in format {{N...M}} where N and M are positive integers, M must be greater than N, with an allowed minimum range of 4", arg)));
}
}; };
if caps.len() == 0 {
return Err(Error::msg("Invalid format"));
}
let mut pattens = Vec::new(); let mut pattens = Vec::new();
while let Some(prefix) = parts.get(1) {
let seq = parse_ellipses_range(parts[2].into())?;
loop { match ELLIPSES_RE.captures(prefix.into()) {
let m = match caps.get(1) { Some(cs) => {
Some(m) => m, pattens.push(Pattern {
None => break, seq,
}; prefix: String::new(),
suffix: parts[3].into(),
let cs = match ELLIPSES_RE.captures(m.into()) { });
Some(cs) => cs, parts = cs;
}
None => { None => {
pattens.push(Pattern {
seq,
prefix: prefix.as_str().to_owned(),
suffix: parts[3].into(),
});
break; break;
} }
}; };
let seq = caps
.get(2)
.map(|m| parse_ellipses_range(m.into()).unwrap_or(Vec::new()))
.unwrap();
let suffix = caps
.get(3)
.map(|m| m.as_str().to_string())
.unwrap_or(String::new());
pattens.push(Pattern {
suffix,
seq,
..Default::default()
});
if cs.len() > 0 {
caps = cs;
continue;
}
break;
} }
if caps.len() > 0 { // Check if any of the prefix or suffixes now have flower braces
let seq = caps // left over, in such a case we generally think that there is
.get(2) // perhaps a typo in users input and error out accordingly.
.map(|m| parse_ellipses_range(m.into()).unwrap_or(Vec::new())) for p in pattens.iter() {
.unwrap(); if p.prefix.contains(OPEN_BRACES)
let suffix = caps || p.prefix.contains(CLOSE_BRACES)
.get(3) || p.suffix.contains(OPEN_BRACES)
.map(|m| m.as_str().to_string()) || p.suffix.contains(CLOSE_BRACES)
.unwrap_or(String::new()); {
let prefix = caps return Err(Error::msg(format!("Invalid ellipsis format in ({}), Ellipsis range must be provided in format {{N...M}} where N and M are positive integers, M must be greater than N, with an allowed minimum range of 4", arg)));
.get(1) }
.map(|m| m.as_str().to_string())
.unwrap_or(String::new());
pattens.push(Pattern {
prefix,
suffix,
seq,
..Default::default()
});
} }
Ok(ArgPattern::new(pattens)) Ok(ArgPattern::new(pattens))
} }
// has_ellipse return ture if has /// returns true if input arg has ellipses type pattern.
#[allow(dead_code)] pub fn has_ellipses<T: AsRef<str>>(s: &[T]) -> bool {
pub fn has_ellipses(s: &Vec<String>) -> bool { let pattern = [ELLIPSES, OPEN_BRACES, CLOSE_BRACES];
let mut ret = true;
for v in s {
ret =
ret && (v.contains(ELLIPSES) || (v.contains(OPEN_BRACES) && v.contains(CLOSE_BRACES)));
}
ret s.iter().any(|v| pattern.iter().any(|p| v.as_ref().contains(p)))
} }
// Parses an ellipses range pattern of following style
// `{1...64}` /// Parses an ellipses range pattern of following style
// `{33...64}` ///
#[allow(dead_code)] /// example:
pub fn parse_ellipses_range(partten: &str) -> Result<Vec<String>> { /// {1...64}
if !partten.contains(OPEN_BRACES) { /// {33...64}
pub fn parse_ellipses_range(pattern: &str) -> Result<Vec<String>> {
if !pattern.contains(OPEN_BRACES) {
return Err(Error::msg("Invalid argument")); return Err(Error::msg("Invalid argument"));
} }
if !partten.contains(OPEN_BRACES) { if !pattern.contains(OPEN_BRACES) {
return Err(Error::msg("Invalid argument")); return Err(Error::msg("Invalid argument"));
} }
let v: Vec<&str> = partten let ellipses_range: Vec<&str> = pattern
.trim_start_matches(OPEN_BRACES) .trim_start_matches(OPEN_BRACES)
.trim_end_matches(CLOSE_BRACES) .trim_end_matches(CLOSE_BRACES)
.split(ELLIPSES) .split(ELLIPSES)
.collect(); .collect();
if v.len() != 2 { if ellipses_range.len() != 2 {
return Err(Error::msg("Invalid argument")); return Err(Error::msg("Invalid argument"));
} }
// let start = usize::from_str_radix(v[0], 16)?; // TODO: Add support for hexadecimals.
// let end = usize::from_str_radix(v[1], 16)?; let start = ellipses_range[0].parse::<usize>()?;
let end = ellipses_range[1].parse::<usize>()?;
let start = v[0].parse::<usize>()?;
let end = v[1].parse::<usize>()?;
if start > end { if start > end {
return Err(Error::msg( return Err(Error::msg("Invalid argument:range start cannot be bigger than end"));
"Invalid argument:range start cannot be bigger than end",
));
} }
let mut ret: Vec<String> = Vec::with_capacity(end + 1); let mut ret: Vec<String> = Vec::with_capacity(end - start + 1);
for i in start..=end {
for i in start..end + 1 { if ellipses_range[0].starts_with('0') && ellipses_range[0].len() > 1 {
if v[0].starts_with('0') && v[0].len() > 1 { ret.push(format!("{:0width$}", i, width = ellipses_range[1].len()));
ret.push(format!("{:0witdth$}", i, witdth = v[0].len()));
} else { } else {
ret.push(format!("{}", i)); ret.push(format!("{}", i));
} }
@@ -224,30 +196,355 @@ mod tests {
#[test] #[test]
fn test_has_ellipses() { fn test_has_ellipses() {
assert_eq!(has_ellipses(vec!["/sdf".to_string()].as_ref()), false); // Tests for all args without ellipses.
assert_eq!(has_ellipses(vec!["{1...3}".to_string()].as_ref()), true); let test_cases = [
} (1, vec!["64"], false),
// Found flower braces, still attempt to parse and throw an error.
(2, vec!["{1..64}"], true),
(3, vec!["{1..2..}"], true),
// Test for valid input.
(4, vec!["1...64"], true),
(5, vec!["{1...2O}"], true),
(6, vec!["..."], true),
(7, vec!["{-1...1}"], true),
(8, vec!["{0...-1}"], true),
(9, vec!["{1....4}"], true),
(10, vec!["{1...64}"], true),
(11, vec!["{...}"], true),
(12, vec!["{1...64}", "{65...128}"], true),
(13, vec!["http://rustfs{2...3}/export/set{1...64}"], true),
(
14,
vec![
"http://rustfs{2...3}/export/set{1...64}",
"http://rustfs{2...3}/export/set{65...128}",
],
true,
),
(15, vec!["mydisk-{a...z}{1...20}"], true),
(16, vec!["mydisk-{1...4}{1..2.}"], true),
];
#[test] for (i, args, expected) in test_cases {
fn test_parse_ellipses_range() { let ret = has_ellipses(&args);
let s = "{1...16}"; assert_eq!(ret, expected, "Test{}: Expected {}, got {}", i, expected, ret);
}
match parse_ellipses_range(s) {
Ok(res) => {
println!("{:?}", res)
}
Err(err) => println!("{err:?}"),
};
} }
#[test] #[test]
fn test_find_ellipses_patterns() { fn test_find_ellipses_patterns() {
use std::result::Result::Ok; #[derive(Default)]
let pattern = "http://rustfs{1...2}:9000/mnt/disk{1...16}"; struct TestCase<'a> {
// let pattern = "http://[2001:3984:3989::{01...f}]/disk{1...10}"; num: usize,
match find_ellipses_patterns(pattern) { pattern: &'a str,
Ok(caps) => println!("caps{caps:?}"), success: bool,
Err(err) => println!("{err:?}"), want: Vec<Vec<&'a str>>,
}
let test_cases = [
TestCase {
num: 1,
pattern: "{1..64}",
..Default::default()
},
TestCase {
num: 2,
pattern: "1...64",
..Default::default()
},
TestCase {
num: 2,
pattern: "...",
..Default::default()
},
TestCase {
num: 3,
pattern: "{1...",
..Default::default()
},
TestCase {
num: 4,
pattern: "...64}",
..Default::default()
},
TestCase {
num: 5,
pattern: "{...}",
..Default::default()
},
TestCase {
num: 6,
pattern: "{-1...1}",
..Default::default()
},
TestCase {
num: 7,
pattern: "{0...-1}",
..Default::default()
},
TestCase {
num: 8,
pattern: "{1...2O}",
..Default::default()
},
TestCase {
num: 9,
pattern: "{64...1}",
..Default::default()
},
TestCase {
num: 10,
pattern: "{1....4}",
..Default::default()
},
TestCase {
num: 11,
pattern: "mydisk-{a...z}{1...20}",
..Default::default()
},
TestCase {
num: 12,
pattern: "mydisk-{1...4}{1..2.}",
..Default::default()
},
TestCase {
num: 13,
pattern: "{1..2.}-mydisk-{1...4}",
..Default::default()
},
TestCase {
num: 14,
pattern: "{{1...4}}",
..Default::default()
},
TestCase {
num: 16,
pattern: "{4...02}",
..Default::default()
},
TestCase {
num: 17,
pattern: "{f...z}",
..Default::default()
},
// Test for valid input.
TestCase {
num: 18,
pattern: "{1...64}",
success: true,
want: vec![
vec!["1"],
vec!["2"],
vec!["3"],
vec!["4"],
vec!["5"],
vec!["6"],
vec!["7"],
vec!["8"],
vec!["9"],
vec!["10"],
vec!["11"],
vec!["12"],
vec!["13"],
vec!["14"],
vec!["15"],
vec!["16"],
vec!["17"],
vec!["18"],
vec!["19"],
vec!["20"],
vec!["21"],
vec!["22"],
vec!["23"],
vec!["24"],
vec!["25"],
vec!["26"],
vec!["27"],
vec!["28"],
vec!["29"],
vec!["30"],
vec!["31"],
vec!["32"],
vec!["33"],
vec!["34"],
vec!["35"],
vec!["36"],
vec!["37"],
vec!["38"],
vec!["39"],
vec!["40"],
vec!["41"],
vec!["42"],
vec!["43"],
vec!["44"],
vec!["45"],
vec!["46"],
vec!["47"],
vec!["48"],
vec!["49"],
vec!["50"],
vec!["51"],
vec!["52"],
vec!["53"],
vec!["54"],
vec!["55"],
vec!["56"],
vec!["57"],
vec!["58"],
vec!["59"],
vec!["60"],
vec!["61"],
vec!["62"],
vec!["63"],
vec!["64"],
],
},
TestCase {
num: 19,
pattern: "{1...5} {65...70}",
success: true,
want: vec![
vec!["1 ", "65"],
vec!["2 ", "65"],
vec!["3 ", "65"],
vec!["4 ", "65"],
vec!["5 ", "65"],
vec!["1 ", "66"],
vec!["2 ", "66"],
vec!["3 ", "66"],
vec!["4 ", "66"],
vec!["5 ", "66"],
vec!["1 ", "67"],
vec!["2 ", "67"],
vec!["3 ", "67"],
vec!["4 ", "67"],
vec!["5 ", "67"],
vec!["1 ", "68"],
vec!["2 ", "68"],
vec!["3 ", "68"],
vec!["4 ", "68"],
vec!["5 ", "68"],
vec!["1 ", "69"],
vec!["2 ", "69"],
vec!["3 ", "69"],
vec!["4 ", "69"],
vec!["5 ", "69"],
vec!["1 ", "70"],
vec!["2 ", "70"],
vec!["3 ", "70"],
vec!["4 ", "70"],
vec!["5 ", "70"],
],
},
TestCase {
num: 20,
pattern: "{01...036}",
success: true,
want: vec![
vec!["001"],
vec!["002"],
vec!["003"],
vec!["004"],
vec!["005"],
vec!["006"],
vec!["007"],
vec!["008"],
vec!["009"],
vec!["010"],
vec!["011"],
vec!["012"],
vec!["013"],
vec!["014"],
vec!["015"],
vec!["016"],
vec!["017"],
vec!["018"],
vec!["019"],
vec!["020"],
vec!["021"],
vec!["022"],
vec!["023"],
vec!["024"],
vec!["025"],
vec!["026"],
vec!["027"],
vec!["028"],
vec!["029"],
vec!["030"],
vec!["031"],
vec!["032"],
vec!["033"],
vec!["034"],
vec!["035"],
vec!["036"],
],
},
TestCase {
num: 21,
pattern: "{001...036}",
success: true,
want: vec![
vec!["001"],
vec!["002"],
vec!["003"],
vec!["004"],
vec!["005"],
vec!["006"],
vec!["007"],
vec!["008"],
vec!["009"],
vec!["010"],
vec!["011"],
vec!["012"],
vec!["013"],
vec!["014"],
vec!["015"],
vec!["016"],
vec!["017"],
vec!["018"],
vec!["019"],
vec!["020"],
vec!["021"],
vec!["022"],
vec!["023"],
vec!["024"],
vec!["025"],
vec!["026"],
vec!["027"],
vec!["028"],
vec!["029"],
vec!["030"],
vec!["031"],
vec!["032"],
vec!["033"],
vec!["034"],
vec!["035"],
vec!["036"],
],
},
];
for test_case in test_cases {
let ret = find_ellipses_patterns(test_case.pattern);
match ret {
Ok(v) => {
if !test_case.success {
panic!("Test{}: Expected failure but passed instead", test_case.num);
}
let got = v.expand();
if got.len() != test_case.want.len() {
panic!("Test{}: Expected {}, got {}", test_case.num, test_case.want.len(), got.len());
}
assert_eq!(got, test_case.want, "Test{}: Expected {:?}, got {:?}", test_case.num, test_case.want, got);
}
Err(e) => {
if test_case.success {
panic!("Test{}: Expected success but failed instead {:?}", test_case.num, e);
}
}
}
} }
} }
} }

3
rustfmt.toml Normal file
View File

@@ -0,0 +1,3 @@
max_width = 130
fn_call_width = 90
single_line_let_else_max_width = 100