use bstr::ByteSlice;
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RemoteProgress<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub action: &'a bstr::BStr,
pub percent: Option<u32>,
pub step: Option<usize>,
pub max: Option<usize>,
}
impl RemoteProgress<'_> {
pub fn from_bytes(mut line: &[u8]) -> Option<RemoteProgress<'_>> {
parse_progress(&mut line).ok().and_then(|r| {
if r.percent.is_none() && r.step.is_none() && r.max.is_none() {
None
} else {
Some(r)
}
})
}
pub fn translate_to_progress(is_error: bool, text: &[u8], progress: &mut impl gix_features::progress::Progress) {
fn progress_name(current: Option<String>, action: &[u8]) -> String {
match current {
Some(current) => format!(
"{}: {}",
current.split_once(':').map_or(&*current, |x| x.0),
action.as_bstr()
),
None => action.as_bstr().to_string(),
}
}
if is_error {
if !text.is_empty() {
progress.fail(progress_name(None, text));
}
} else {
match RemoteProgress::from_bytes(text) {
Some(RemoteProgress {
action,
percent: _,
step,
max,
}) => {
progress.set_name(progress_name(progress.name(), action));
progress.init(max, gix_features::progress::count("objects"));
if let Some(step) = step {
progress.set(step);
}
}
None => progress.set_name(progress_name(progress.name(), text)),
}
}
}
}
fn parse_number(i: &mut &[u8]) -> Option<usize> {
let len = i.iter().take_while(|b| b.is_ascii_digit()).count();
if len == 0 {
return None;
}
let (number, rest) = i.split_at(len);
*i = rest;
gix_utils::btoi::to_signed(number).ok()
}
fn skip_until_digit_or_to_end(i: &mut &[u8]) {
let pos = i.iter().position(u8::is_ascii_digit).unwrap_or(i.len());
*i = &i[pos..];
}
fn next_optional_percentage(i: &mut &[u8]) -> Option<u32> {
let before = *i;
skip_until_digit_or_to_end(i);
let number = parse_number(i)?;
if let Some(rest) = i.strip_prefix(b"%") {
*i = rest;
u32::try_from(number).ok()
} else {
*i = before;
None
}
}
fn next_optional_number(i: &mut &[u8]) -> Option<usize> {
skip_until_digit_or_to_end(i);
parse_number(i)
}
fn parse_progress<'i>(line: &mut &'i [u8]) -> Result<RemoteProgress<'i>, ()> {
let action_end = line.iter().position(|b| *b == b':').ok_or(())?;
if action_end == 0 {
return Err(());
}
let action = &line[..action_end];
*line = &line[action_end..];
let percent = next_optional_percentage(line);
let step = next_optional_number(line);
let max = next_optional_number(line);
Ok(RemoteProgress {
action: action.into(),
percent,
step,
max,
})
}