#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::progress::{ProgressBar, ProgressStyle};
use std::io::IsTerminal;
use std::time::{Duration, Instant};
pub struct ProgressIndicator {
progress_bar: Option<ProgressBar>,
}
impl ProgressIndicator {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(message: &str) -> Self {
let progress_bar = if Self::should_show_progress() {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
.template("{spinner:.cyan} {msg}")
.expect("internal error"),
);
pb.set_message(message.to_string());
pb.enable_steady_tick(Duration::from_millis(100));
Some(pb)
} else {
None
};
Self { progress_bar }
}
fn should_show_progress() -> bool {
if std::env::var("CI").is_ok() {
return false;
}
if std::env::var("NO_COLOR").is_ok() {
return false;
}
if std::env::var("PMAT_QUIET").is_ok() {
return false;
}
std::io::stdout().is_terminal()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn set_message(&self, message: &str) {
if let Some(ref pb) = self.progress_bar {
pb.set_message(message.to_string());
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn finish_with_message(&self, message: &str) {
if let Some(ref pb) = self.progress_bar {
pb.finish_with_message(format!("✓ {}", message));
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn finish_with_error(&self, message: &str) {
if let Some(ref pb) = self.progress_bar {
pb.finish_with_message(format!("✗ {}", message));
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn clear(&self) {
if let Some(ref pb) = self.progress_bar {
pb.finish_and_clear();
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn is_enabled(&self) -> bool {
self.progress_bar.is_some()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn uses_color(&self) -> bool {
Self::should_show_progress() && std::env::var("NO_COLOR").is_err()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn is_tty() -> bool {
std::io::stdout().is_terminal()
}
}
impl Drop for ProgressIndicator {
fn drop(&mut self) {
self.clear();
}
}
pub struct MultiStageProgress {
stages: Vec<String>,
current_stage_index: usize,
progress_bar: Option<ProgressBar>,
start_time: Instant,
completed_items: u64,
total_items: u64,
}
impl MultiStageProgress {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(stages: Vec<String>) -> Self {
Self {
stages,
current_stage_index: 0,
progress_bar: None,
start_time: Instant::now(),
completed_items: 0,
total_items: 0,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn next_stage(&mut self, _message: &str) {
if self.current_stage_index < self.stages.len() - 1 {
self.current_stage_index += 1;
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn current_stage(&self) -> &str {
&self.stages[self.current_stage_index]
}
pub fn current_stage_index(&self) -> usize {
self.current_stage_index
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn set_progress(&mut self, current: u64, total: u64) {
self.completed_items = current;
self.total_items = total;
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn completed_items(&self) -> u64 {
self.completed_items
}
pub fn total_items(&self) -> u64 {
self.total_items
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_eta(&self) -> Duration {
if self.completed_items == 0 || self.total_items == 0 {
return Duration::from_secs(0);
}
let elapsed = self.start_time.elapsed();
let items_remaining = self.total_items.saturating_sub(self.completed_items);
let time_per_item = elapsed.as_secs_f64() / self.completed_items as f64;
let estimated_seconds = (items_remaining as f64 * time_per_item) as u64;
Duration::from_secs(estimated_seconds)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn finish(&self, _message: &str) {
if let Some(ref pb) = self.progress_bar {
pb.finish_and_clear();
}
}
}
pub struct CategoryProgress {
categories: Vec<String>,
current_category_index: usize,
files_processed: usize,
total_files: usize,
progress_bar: Option<ProgressBar>,
start_time: Instant,
}
impl CategoryProgress {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(categories: Vec<String>) -> Self {
Self {
categories,
current_category_index: 0,
files_processed: 0,
total_files: 0,
progress_bar: None,
start_time: Instant::now(),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn next_category(&mut self, _name: &str) {
if self.current_category_index < self.categories.len() - 1 {
self.current_category_index += 1;
}
self.files_processed = 0;
self.total_files = 0;
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn current_category(&self) -> &str {
&self.categories[self.current_category_index]
}
pub fn current_category_index(&self) -> usize {
self.current_category_index
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn set_file_progress(&mut self, current: usize, total: usize) {
self.files_processed = current;
self.total_files = total;
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn files_processed(&self) -> usize {
self.files_processed
}
pub fn total_files(&self) -> usize {
self.total_files
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn category_percent(&self) -> f64 {
if self.total_files == 0 {
return 0.0;
}
(self.files_processed as f64 / self.total_files as f64) * 100.0
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn overall_percent(&self) -> f64 {
if self.categories.is_empty() {
return 0.0;
}
let completed_categories = self.current_category_index as f64;
let current_category_progress = self.category_percent() / 100.0;
let total_progress = completed_categories + current_category_progress;
(total_progress / self.categories.len() as f64) * 100.0
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn elapsed(&self) -> Duration {
self.start_time.elapsed()
}
pub fn finish(&self) {
if let Some(ref pb) = self.progress_bar {
pb.finish_and_clear();
}
}
}
pub struct Spinner {
frames: Vec<char>,
current_frame_index: usize,
}
impl Spinner {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self {
frames: vec!['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
current_frame_index: 0,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn tick(&mut self) {
self.current_frame_index = (self.current_frame_index + 1) % self.frames.len();
}
pub fn current_frame(&self) -> char {
self.frames[self.current_frame_index]
}
}
impl Default for Spinner {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_indicator_creation() {
let progress = ProgressIndicator::new("Testing...");
assert!(progress.progress_bar.is_none() || progress.progress_bar.is_some());
}
#[test]
fn test_progress_indicator_messages() {
let progress = ProgressIndicator::new("Initial");
progress.set_message("Updated");
progress.finish_with_message("Done");
}
#[test]
fn test_progress_indicator_error() {
let progress = ProgressIndicator::new("Working");
progress.finish_with_error("Failed");
}
#[test]
fn test_should_show_progress_respects_ci() {
let _should_show = ProgressIndicator::should_show_progress();
}
}