gix_protocol/
remote_progress.rs1use bstr::ByteSlice;
2use winnow::{
3 combinator::{opt, preceded, terminated},
4 prelude::*,
5 token::take_till,
6};
7
8#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct RemoteProgress<'a> {
13 #[cfg_attr(feature = "serde", serde(borrow))]
14 pub action: &'a bstr::BStr,
16 pub percent: Option<u32>,
18 pub step: Option<usize>,
20 pub max: Option<usize>,
22}
23
24impl RemoteProgress<'_> {
25 pub fn from_bytes(mut line: &[u8]) -> Option<RemoteProgress<'_>> {
27 parse_progress(&mut line).ok().and_then(|r| {
28 if r.percent.is_none() && r.step.is_none() && r.max.is_none() {
29 None
30 } else {
31 Some(r)
32 }
33 })
34 }
35
36 pub fn translate_to_progress(is_error: bool, text: &[u8], progress: &mut impl gix_features::progress::Progress) {
39 fn progress_name(current: Option<String>, action: &[u8]) -> String {
40 match current {
41 Some(current) => format!(
42 "{}: {}",
43 current.split_once(':').map_or(&*current, |x| x.0),
44 action.as_bstr()
45 ),
46 None => action.as_bstr().to_string(),
47 }
48 }
49 if is_error {
50 if !text.is_empty() {
52 progress.fail(progress_name(None, text));
53 }
54 } else {
55 match RemoteProgress::from_bytes(text) {
56 Some(RemoteProgress {
57 action,
58 percent: _,
59 step,
60 max,
61 }) => {
62 progress.set_name(progress_name(progress.name(), action));
63 progress.init(max, gix_features::progress::count("objects"));
64 if let Some(step) = step {
65 progress.set(step);
66 }
67 }
68 None => progress.set_name(progress_name(progress.name(), text)),
69 };
70 }
71 }
72}
73
74fn parse_number(i: &mut &[u8]) -> PResult<usize, ()> {
75 take_till(0.., |c: u8| !c.is_ascii_digit())
76 .try_map(gix_utils::btoi::to_signed)
77 .parse_next(i)
78}
79
80fn next_optional_percentage(i: &mut &[u8]) -> PResult<Option<u32>, ()> {
81 opt(terminated(
82 preceded(
83 take_till(0.., |c: u8| c.is_ascii_digit()),
84 parse_number.try_map(u32::try_from),
85 ),
86 b"%",
87 ))
88 .parse_next(i)
89}
90
91fn next_optional_number(i: &mut &[u8]) -> PResult<Option<usize>, ()> {
92 opt(preceded(take_till(0.., |c: u8| c.is_ascii_digit()), parse_number)).parse_next(i)
93}
94
95fn parse_progress<'i>(line: &mut &'i [u8]) -> PResult<RemoteProgress<'i>, ()> {
96 let action = take_till(1.., |c| c == b':').parse_next(line)?;
97 let percent = next_optional_percentage.parse_next(line)?;
98 let step = next_optional_number.parse_next(line)?;
99 let max = next_optional_number.parse_next(line)?;
100 Ok(RemoteProgress {
101 action: action.into(),
102 percent,
103 step,
104 max,
105 })
106}