• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lpenz / ogle / 17025475461

17 Aug 2025 08:23PM UTC coverage: 60.823% (-0.9%) from 61.725%
17025475461

push

github

lpenz
view: make the state a first-class field

Sleep deadline is now inside the sleep state.

0 of 51 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

458 of 753 relevant lines covered (60.82%)

1.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/view.rs
1
// Copyright (C) 2025 Leandro Lisboa Penz <lpenz@lpenz.org>
2
// This file is subject to the terms and conditions defined in
3
// file 'LICENSE', which is part of this source code package.
4

5
use pin_project::pin_project;
6
use std::collections::VecDeque;
7
use std::pin::Pin;
8
use std::task::{Context, Poll};
9
use tokio_stream::Stream;
10

11
use crate::differ::Differ;
12
use crate::engine::EData;
13
use crate::engine::EItem;
14
use crate::engine::Engine;
15
use crate::output::ClearLine;
16
use crate::output::MoveCursorUp;
17
use crate::output::OutputCommand;
18
use crate::output::WriteAll;
19
use crate::process_wrapper::Cmd;
20
use crate::progbar::progbar_running;
21
use crate::progbar::progbar_sleeping;
22
use crate::progbar::spinner_get;
23
use crate::sys::SysApi;
24
use crate::time_wrapper::Duration;
25
use crate::time_wrapper::Instant;
26

27
#[derive(Debug, Clone, Copy)]
28
pub enum State {
29
    Running,
30
    Sleeping {
31
        /// Approximate time when we'll wake up with the next StartRun,
32
        /// used for the countdown.
33
        deadline: Instant,
34
    },
35
}
36

37
#[pin_project(project = ViewProjection)]
38
pub struct View<SI: SysApi> {
39
    // Configuration parameters:
40
    cmd: Cmd,
41
    refresh: Duration,
42
    sleep: Duration,
43
    /// The engine that streams the all events.
44
    engine: Engine<SI>,
45
    /// Some engine events generate more than one item; store them
46
    /// here and yield them in the next calls.
47
    pending: VecDeque<OutputCommand>,
48
    /// The differ that stores the lines so that we can compare runs.
49
    differ: Differ,
50
    /// Spinner state
51
    spinner: char,
52
    start: Instant, // can be start of running or sleep
53
    duration: Option<Duration>,
54
    /// If the status is currently visible and has to be cleared.
55
    printed_status: bool,
56
    /// Number of runs so far.
57
    runs: u32,
58
    /// Current state
59
    state: State,
60
}
61

62
impl<SI: SysApi> View<SI> {
63
    pub fn new(cmd: Cmd, refresh: Duration, sleep: Duration, engine: Engine<SI>) -> Self {
×
64
        View {
×
65
            cmd,
×
66
            refresh,
×
67
            sleep,
×
68
            engine,
×
69
            pending: VecDeque::default(),
×
70
            differ: Differ::default(),
×
71
            spinner: '-',
×
72
            start: Instant::default(),
×
73
            duration: None,
×
74
            printed_status: false,
×
75
            runs: 0,
×
NEW
76
            state: State::Sleeping {
×
NEW
77
                deadline: Default::default(),
×
NEW
78
            },
×
79
        }
×
80
    }
×
81
}
82

83
impl<SI: SysApi> ViewProjection<'_, SI> {
84
    fn _println(&mut self, mut s: String) {
×
85
        s.push('\n');
×
86
        self.pending
×
87
            .push_back(OutputCommand::WriteAll(WriteAll(s.as_bytes().to_vec())))
×
88
    }
×
89

90
    fn status_maybe_clear(&mut self) {
×
91
        if *self.printed_status {
×
92
            self.pending
×
93
                .push_back(OutputCommand::MoveCursorUp(MoveCursorUp(1)));
×
94
            self.pending
×
95
                .push_back(OutputCommand::ClearLine(ClearLine {}));
×
96
        }
×
97
    }
×
98

99
    fn println(&mut self, s: String) {
×
100
        self.status_maybe_clear();
×
101
        self._println(s);
×
102
        *self.printed_status = false;
×
103
    }
×
104

105
    fn process_line(&mut self, line: String) {
×
106
        self.differ.push(line);
×
107
        let mut differ = std::mem::take(self.differ);
×
108
        if differ.has_changed() {
×
109
            for line in &mut differ {
×
110
                self.println(line);
×
111
            }
×
112
        }
×
113
        *self.differ = differ;
×
114
    }
×
115

116
    fn status_update_running(&mut self, now: Instant) {
×
117
        self.status_maybe_clear();
×
118
        let mut spinner = *self.spinner;
×
119
        self._println(ofmt!(
×
120
            &now,
×
121
            "{}",
×
122
            progbar_running(
×
123
                150,                       // width: usize,
124
                &now,                      // now: &Instant,
×
125
                self.start,                // start: &Instant,
×
126
                *self.duration,            // duration: Option<&Duration>,
×
127
                self.refresh,              // refresh: &Duration,
×
128
                spinner_get(&mut spinner)  // spinner: char,
×
129
            )
130
            .unwrap()
×
131
        ));
132
        *self.spinner = spinner;
×
133
        *self.printed_status = true;
×
134
    }
×
135

136
    fn status_update_sleeping(&mut self, now: Instant, deadline: Instant) {
×
137
        self.status_maybe_clear();
×
138
        let mut spinner = *self.spinner;
×
139
        self._println(ofmt!(
×
140
            self.start,
×
141
            "{}",
×
142
            progbar_sleeping(self.sleep, &now, &deadline, spinner_get(&mut spinner))
×
143
        ));
144
        *self.spinner = spinner;
×
145
        *self.printed_status = true;
×
146
    }
×
147
}
148

149
impl<SI: SysApi> Stream for View<SI> {
150
    type Item = OutputCommand;
151

152
    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
×
153
        let mut this = self.as_mut().project();
×
154
        if let Some(output) = this.pending.pop_front() {
×
155
            return Poll::Ready(Some(output));
×
156
        }
×
157
        let item = Pin::new(&mut this.engine).poll_next(cx);
×
NEW
158
        match this.state {
×
159
            State::Running => {
NEW
160
                match item {
×
NEW
161
                    Poll::Pending => Poll::Pending,
×
NEW
162
                    Poll::Ready(None) => Poll::Ready(None),
×
NEW
163
                    Poll::Ready(Some(EItem { time: now, data })) => match data {
×
NEW
164
                        EData::StartSleep(deadline) => {
×
NEW
165
                            *this.state = State::Sleeping { deadline };
×
NEW
166
                            self.poll_next(cx)
×
167
                        }
NEW
168
                        EData::LineOut(line) => {
×
NEW
169
                            this.process_line(line);
×
NEW
170
                            this.status_update_running(now);
×
NEW
171
                            self.poll_next(cx)
×
172
                        }
NEW
173
                        EData::LineErr(line) => {
×
NEW
174
                            this.process_line(line);
×
NEW
175
                            this.status_update_running(now);
×
NEW
176
                            self.poll_next(cx)
×
177
                        }
NEW
178
                        EData::Done(sts) => {
×
NEW
179
                            let line = ofmt_timeless!("exited with {}", sts);
×
NEW
180
                            this.process_line(line);
×
NEW
181
                            *this.duration = Some(&now - this.start);
×
182
                            // Sleeping starts now
NEW
183
                            *this.start = now;
×
NEW
184
                            *this.runs += 1;
×
NEW
185
                            self.poll_next(cx)
×
186
                        }
NEW
187
                        EData::Err(e) => {
×
NEW
188
                            this.println(ofmt!(&now, "err {:?}", e));
×
NEW
189
                            self.poll_next(cx)
×
190
                        }
191
                        EData::Tick => {
NEW
192
                            this.status_update_running(now);
×
NEW
193
                            self.poll_next(cx)
×
194
                        }
195
                        _ => {
NEW
196
                            panic!("unexpected data while running: {:?}", data);
×
197
                        }
198
                    },
199
                }
200
            }
NEW
201
            State::Sleeping { deadline } => match item {
×
NEW
202
                Poll::Pending => Poll::Pending,
×
NEW
203
                Poll::Ready(None) => Poll::Ready(None),
×
NEW
204
                Poll::Ready(Some(EItem { time: now, data })) => match data {
×
205
                    EData::StartRun => {
NEW
206
                        if *this.runs == 0 {
×
NEW
207
                            this.println(ofmt!(&now, "start execution"));
×
NEW
208
                        }
×
NEW
209
                        this.differ.reset();
×
NEW
210
                        *this.start = now;
×
211
                        this.status_update_running(now);
×
NEW
212
                        this.process_line(ofmt_timeless!("+ {}", this.cmd));
×
NEW
213
                        *this.state = State::Running;
×
NEW
214
                        self.poll_next(cx)
×
215
                    }
NEW
216
                    EData::Err(e) => {
×
NEW
217
                        this.println(ofmt!(&now, "err {:?}", e));
×
NEW
218
                        self.poll_next(cx)
×
219
                    }
220
                    EData::Tick => {
NEW
221
                        let deadline = *deadline;
×
NEW
222
                        this.status_update_sleeping(now, deadline);
×
NEW
223
                        self.poll_next(cx)
×
224
                    }
225
                    _ => {
NEW
226
                        panic!("unexpected data while sleeping: {:?}", data);
×
227
                    }
228
                },
229
            },
230
        }
UNCOV
231
    }
×
232
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc