Skip to content

Commit

Permalink
[wip] output_sequence
Browse files Browse the repository at this point in the history
  • Loading branch information
lpenz committed Jan 29, 2025
1 parent bf016a7 commit 7fc552e
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod misc;

mod output_trait;

mod output_sequence;
mod output_simple;

mod cli;
Expand Down
169 changes: 169 additions & 0 deletions src/output_sequence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (C) 2025 Leandro Lisboa Penz <lpenz@lpenz.org>
// This file is subject to the terms and conditions defined in
// file 'LICENSE', which is part of this source code package.

use color_eyre::Result;
use console::Term;
use std::io::Write;
use std::process::ExitStatus;
use tracing::instrument;

use crate::cli::Cli;
use crate::misc::term_clear_line;
use crate::misc::term_width;
use crate::output_trait::Output;
use crate::progbar;
use crate::timewrap::Duration;
use crate::timewrap::Instant;

#[derive(Debug, Default)]
pub enum State {
#[default]
Starting,
Sleeping,
Running,
}

#[derive(Debug)]
pub struct OutputSequence {
term: Term,
width: usize,
state: State,
start: Instant,
sleep_duration: Duration,
run_duration: Option<Duration>,
refresh: Duration,
lines: Vec<String>,
iline: usize,
already_different: bool,
}

impl OutputSequence {
#[instrument(level = "debug")]
pub fn new(cli: &Cli) -> Self {
let term = Term::stdout();
let width = term_width(&term);
Self {
term,
width,
state: State::default(),
start: Instant::now(),
sleep_duration: Duration::seconds(cli.period as i64),
run_duration: None,
refresh: Duration::milliseconds(250),
lines: vec![],
iline: 0,
already_different: true,
}
}

fn write_line(&mut self, line: &str) -> Result<()> {
self.term.write_all(line.as_bytes())?;
self.term.write_all(b"\n")?;
Ok(())
}

fn write_line_scroll(&mut self, line: &str) -> Result<()> {
self.write_line(line)?;
self.term.write_all(b"\n")?;
self.term.flush()?;
Ok(())
}

fn write_all_lines(&mut self) -> Result<()> {
let mut lines = std::mem::take(&mut self.lines);
for line in &lines {
self.write_line(line)?;
}
self.term.flush()?;
self.lines = std::mem::take(&mut lines);
Ok(())
}
}

impl Output for OutputSequence {
#[instrument(level = "debug", fields(self=?self.state))]
fn run_start(&mut self) -> Result<()> {
let now = Instant::now();
if self.run_duration.is_none() {
// First execution
self.write_line_scroll(&ofmt!(&now, "first execution"))?;
}
self.state = State::Running;
self.start = now;
// Let's refresh the terminal width every time we start
// running the program.
self.width = term_width(&self.term);
self.iline = 0;
self.tick()?;
Ok(())
}

#[instrument(level = "debug", skip(self))]
fn run_end(&mut self, exitstatus: &ExitStatus) -> Result<()> {
let now = Instant::now();
self.run_duration = Some(&now - &self.start);
self.state = State::Sleeping;
self.start = now;
self.iline = 0;
self.already_different = false;
self.tick()?;
Ok(())
}

#[instrument(level = "debug", skip(self))]
fn out_line(&mut self, line: String) -> Result<()> {
self.iline += 1;
if self.iline < self.lines.len() && self.lines[self.iline - 1] == line {
// Same as last execution, keep going
return self.tick();
}
// Something is different
term_clear_line(&self.term)?;
if !self.already_different {
self.write_line(&ofmt!(&Instant::now(), "changed"))?;
self.lines.truncate(self.iline - 1);
self.write_all_lines()?;
self.already_different = true;
}
self.write_line_scroll(&line)?;
self.lines.push(line);
self.tick()?;
Ok(())
}

#[instrument(level = "debug", skip(self))]
fn err_line(&mut self, line: String) -> Result<()> {
self.out_line(line)
}

// #[instrument(level = "debug", skip(self))]
fn tick(&mut self) -> Result<()> {
let now = Instant::now();
match self.state {
State::Starting => {}
State::Sleeping => {
term_clear_line(&self.term)?;
self.term.write_line(&progbar::progbar_sleeping(
&self.start,
&now,
&self.start,
&self.sleep_duration,
)?)?;
}
State::Running => {
term_clear_line(&self.term)?;
self.term.write_line(&progbar::progbar_running(
self.width,
&now,
&now,
&self.start,
&self.run_duration,
&self.refresh,
' ',
)?)?;
}
}
Ok(())
}
}
43 changes: 23 additions & 20 deletions src/progbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,32 @@ pub fn progbar_running(
spinner: char,
) -> Result<String> {
let duration = duration.unwrap_or_default();
let dur = duration.num_milliseconds();
let msg = if dur <= 3000 {
let duration_millis = duration.num_milliseconds();
if duration_millis == 0 || refresh.num_milliseconds() == 0 {
return Ok(ofmt!(timestamp, "running [{}]", spinner));
}
let head = ofmt!(timestamp, "running ");
let tail = format!(" [{}]", spinner);
let barsize = {
let b = (duration_millis / refresh.num_milliseconds()) as usize;
let overhead = head.len() + tail.len() + 1;
debug_assert!(
width >= overhead,
"width {} not greater than overhead {}",
width,
overhead
);
if b + overhead > width {
width - overhead
} else {
b
}
};
let msg = if barsize <= 1 {
ofmt!(timestamp, "running [{}]", spinner)
} else {
let head = ofmt!(timestamp, "running ");
let tail = format!(" [{}]", spinner);
let barsize = {
let b = (dur / refresh.num_milliseconds()) as usize;
let overhead = head.len() + tail.len() + 1;
debug_assert!(
width >= overhead,
"width {} not greater than overhead {}",
width,
overhead
);
if b + overhead > width {
width - overhead
} else {
b
}
};
let elapsed = now - start;
let ratio = elapsed.num_milliseconds() as f32 / dur as f32;
let ratio = elapsed.num_milliseconds() as f32 / duration_millis as f32;
let left = if ratio < 1_f32 {
((barsize as f32) * ratio).ceil() as usize
} else {
Expand Down
27 changes: 27 additions & 0 deletions src/termapi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (C) 2025 Leandro Lisboa Penz <lpenz@lpenz.org>
// This file is subject to the terms and conditions defined in
// file 'LICENSE', which is part of this source code package.

// Useful functions:

const SPINNERS: [char; 4] = ['/', '-', '\\', '|'];

#[derive(Debug)]
pub struct TermWrapper {
term: Term,
}

impl TermWrapper {
fn width(&self) -> usize {
if let Some((_, w)) = self.term.size_checked() {
w as usize
} else {
80
}
}

pub fn clear_line(&mut self) -> Result<()> {
self.term.move_cursor_up(1)?;
self.term.clear_line()?;
}
}

0 comments on commit 7fc552e

Please sign in to comment.