#![warn(clippy::pedantic, missing_docs)]
use std::{
borrow::Cow,
fmt::{Display, Write},
};
use ansi::{ClearLine, CursorUp};
use downcast::AnySync;
use util::DisplayFn;
pub mod ansi;
mod loops;
pub mod spinner;
mod util;
pub use loops::Loop;
pub use util::SharedFrames;
pub trait Frames: AnySync + core::fmt::Display {
fn advance(&mut self);
fn reset(&mut self) {}
#[allow(clippy::missing_errors_doc)]
fn clear(&self, _f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
Ok(())
}
fn lines(&self) -> usize {
0
}
fn print_len(&self) -> Option<usize> {
None
}
}
downcast::downcast_sync!(dyn Frames);
#[must_use]
#[derive(Default)]
pub struct Group {
indent: usize,
frames: Vec<Box<dyn Frames>>,
}
impl Group {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.frames.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn insert(&mut self, idx: usize, frames: impl Frames) -> &mut Self {
self.frames.insert(idx, Box::new(frames));
self
}
pub fn push(&mut self, frames: impl Frames) -> &mut Self {
self.frames.push(Box::new(frames));
self
}
pub fn extend<I>(&mut self, iter: I) -> &mut Self
where
I: IntoIterator,
I::Item: Frames,
{
for f in iter {
self.push(f);
}
self
}
pub fn remove(&mut self, idx: usize) -> &mut Self {
self.frames.remove(idx);
self
}
pub fn iter(&self) -> impl Iterator<Item = &dyn Frames> + '_ {
self.frames.iter().map(|s| &**s)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut dyn Frames> + '_ {
self.frames.iter_mut().map(|s| &mut **s)
}
pub fn retain(&mut self, f: impl Fn(&dyn Frames) -> bool) {
self.frames.retain(move |s| f(&**s));
}
#[must_use]
pub fn clear(&self) -> impl core::fmt::Display + '_ {
DisplayFn::new(|f| <Self as Frames>::clear(self, f))
}
#[must_use]
pub fn get_indent(&self) -> usize {
self.indent
}
pub fn with_indent(mut self, level: usize) -> Self {
self.indent = level;
self
}
pub fn set_indent(&mut self, level: usize) -> &mut Self {
self.indent = level;
self
}
pub fn shared(self) -> SharedFrames<Self> {
SharedFrames::new(self)
}
}
impl core::fmt::Display for Group {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for spinner in &self.frames {
if spinner.lines() > 0 {
for _ in 0..self.indent {
" ".fmt(f)?;
}
}
spinner.fmt(f)?;
for _ in 0..spinner.lines() {
'\n'.fmt(f)?;
}
}
Ok(())
}
}
impl Frames for Group {
fn advance(&mut self) {
for spinner in &mut self.frames {
spinner.advance();
}
}
fn reset(&mut self) {
for spinner in &mut self.frames {
spinner.reset();
}
}
fn clear(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
for spinner in self.frames.iter().rev() {
CursorUp(spinner.lines()).fmt(f)?;
spinner.clear(f)?;
}
Ok(())
}
}
#[must_use]
pub struct Line {
show_spinner: bool,
spinner: Box<dyn Frames>,
text: Cow<'static, str>,
}
impl Line {
pub fn new(spinner: impl Frames) -> Self {
Self {
spinner: Box::new(spinner),
show_spinner: true,
text: Cow::Borrowed(""),
}
}
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
pub fn set_text(&mut self, text: &str) -> &mut Self {
self.text = text.to_string().into();
self
}
pub fn with_text(mut self, text: &str) -> Self {
self.text = text.to_string().into();
self
}
pub fn set_spinner_visible(&mut self, show: bool) -> &mut Self {
self.show_spinner = show;
self
}
pub fn with_spinner_visible(mut self, show: bool) -> Self {
self.show_spinner = show;
self
}
#[must_use]
pub fn clear(&self) -> impl core::fmt::Display + '_ {
DisplayFn::new(|f| <Self as Frames>::clear(self, f))
}
pub fn shared(self) -> SharedFrames<Self> {
SharedFrames::new(self)
}
}
impl Frames for Line {
fn advance(&mut self) {
self.spinner.advance();
}
fn reset(&mut self) {
self.spinner.reset();
}
fn clear(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
"\r".fmt(f)?;
ClearLine.fmt(f)
}
fn lines(&self) -> usize {
1
}
}
impl core::fmt::Display for Line {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.show_spinner {
self.spinner.fmt(f)?;
let spinner_printed = self.spinner.print_len().map_or(true, |l| l != 0);
if spinner_printed && !self.text.is_empty() {
f.write_char(' ')?;
}
}
self.text.fmt(f)?;
Ok(())
}
}