Compare commits
16 Commits
Author | SHA1 | Date |
---|---|---|
Avril | 1a8792b4d0 | 4 years ago |
Avril | dd8ed72b26 | 4 years ago |
Avril | 1f96af62d6 | 4 years ago |
Avril | 72bfc293cd | 4 years ago |
Avril | 950bf6db88 | 4 years ago |
Avril | e4ff08ef8f | 4 years ago |
Avril | 887da9ef35 | 4 years ago |
Avril | 5bb9ee59af | 4 years ago |
Avril | 34ff9847cb | 4 years ago |
Avril | 58643b6440 | 4 years ago |
Avril | 98ff368fe8 | 4 years ago |
Avril | faece04d4e | 4 years ago |
Avril | 88defc530e | 4 years ago |
Avril | 6a2570a0c4 | 4 years ago |
Avril | 583b5fd0e6 | 4 years ago |
Avril | b6dc149413 | 4 years ago |
@ -1,2 +1 @@
|
|||||||
Add lolistealer-like async progress bar handler to State (check state.rs)
|
Rework logging macro calls to use new progress API
|
||||||
|
|
||||||
|
@ -0,0 +1,429 @@
|
|||||||
|
//! CLI flags module
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
any::Any,
|
||||||
|
error,
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum Kind<'a>
|
||||||
|
{
|
||||||
|
/// Any number of these long-form arguments starting with `--`, and flag for case-sensitivity.
|
||||||
|
/// Can be empty to match any argument (not needing a prefix).
|
||||||
|
Long(&'a [&'a str], bool),
|
||||||
|
/// Any number of these short-form (single character) arguments together or apart (starting with `-`).
|
||||||
|
/// Can be empty for a single `-` character, and a flag for case-sensitivity.
|
||||||
|
Short(&'a [(char, )], bool),
|
||||||
|
/// A single `--`.
|
||||||
|
Terminator,
|
||||||
|
/// An exact match with no prefix.
|
||||||
|
Exact(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kind<'static>
|
||||||
|
{
|
||||||
|
pub fn as_ref<'a>(&'a self) -> Kind<'a>
|
||||||
|
{
|
||||||
|
match &self {
|
||||||
|
Self::Long(strs, bl) => Kind::Long(strs, *bl),
|
||||||
|
Self::Short(chrs, bl) => Kind::Short(chrs, *bl),
|
||||||
|
Self::Terminator => Kind::Terminator,
|
||||||
|
Self::Exact(stri) => Kind::Exact(&stri[..]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum ValueKind
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Equals,
|
||||||
|
Next(usize),
|
||||||
|
Rest,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArgState<T=()>
|
||||||
|
{
|
||||||
|
user: T,
|
||||||
|
|
||||||
|
flags: Vec<(Kind<'static>, Cow<'static, str>, ValueKind, Box<(dyn FlagCallback<T, Box<dyn Any + 'static>, eyre::Report> + 'static)>, bool, bool)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FlagCallback<U, T,E>: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result<T, E>{}
|
||||||
|
impl<U, F, T, E> FlagCallback<U, T,E> for F
|
||||||
|
where F: FnMut(&mut StateHandle<'_, U>, usize, &str) -> Result<T, E>,
|
||||||
|
T: Any + 'static,
|
||||||
|
E: Into<eyre::Report> {}
|
||||||
|
|
||||||
|
impl<U> ArgState<U>
|
||||||
|
{
|
||||||
|
/// Push a handle to the state
|
||||||
|
pub fn push<T: Any + 'static,E: Into<eyre::Report>, F>(&mut self, kind: Kind<'static>, desc: impl Into<Cow<'static, str>>, value: ValueKind, mut handle: F, single: bool) -> usize
|
||||||
|
where F: FlagCallback<U, T,E> + 'static
|
||||||
|
{
|
||||||
|
let handle: Box<dyn FlagCallback<U, Box<(dyn Any + 'static)>, eyre::Report>> =
|
||||||
|
Box::new(move |state, index, value| handle(state, index, value)
|
||||||
|
.map(|x| -> Box<(dyn Any + 'static)> {Box::new(x)})
|
||||||
|
.map_err(Into::into));
|
||||||
|
self.flags.push((kind, desc.into(), value, handle, single, true));
|
||||||
|
self.flags.len()-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Either<T,U>
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
One(T),
|
||||||
|
Many(U),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T,U> Either<T,U>
|
||||||
|
{
|
||||||
|
pub fn take(&mut self) -> Self
|
||||||
|
{
|
||||||
|
std::mem::replace(self, Self::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum EitherIter<T,U>
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
One(std::iter::Once<T>),
|
||||||
|
Many(std::iter::Fuse<U>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T,U> Iterator for EitherIter<T,U>
|
||||||
|
where U: Iterator<Item=T>
|
||||||
|
{
|
||||||
|
type Item = T;
|
||||||
|
fn next(&mut self) -> Option<Self::Item>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::None => None,
|
||||||
|
Self::One(one) => one.next(),
|
||||||
|
Self::Many(many) => many.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
match self {
|
||||||
|
Self::None => (0, Some(0)),
|
||||||
|
Self::One(_) => (1, Some(1)),
|
||||||
|
Self::Many(many) => many.size_hint(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T,U: Iterator<Item=T>> std::iter::FusedIterator for EitherIter<T,U>{}
|
||||||
|
impl<T,U: Iterator<Item=T>> std::iter::ExactSizeIterator for EitherIter<T,U>
|
||||||
|
where U: ExactSizeIterator{}
|
||||||
|
|
||||||
|
impl<T, U: IntoIterator<Item=T>> IntoIterator for Either<T, U>
|
||||||
|
{
|
||||||
|
type Item= T;
|
||||||
|
type IntoIter = EitherIter<T, <U as IntoIterator>::IntoIter>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::None => EitherIter::None,
|
||||||
|
Self::One(one) => EitherIter::One(std::iter::once(one)),
|
||||||
|
Self::Many(many) => EitherIter::Many(many.into_iter().fuse())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A handle to mutate the arg state.
|
||||||
|
pub struct StateHandle<'a, T>
|
||||||
|
{
|
||||||
|
held: Either<String, Vec<String>>,
|
||||||
|
|
||||||
|
state: &'a mut T,
|
||||||
|
args: &'a mut (dyn Iterator<Item=String> + 'a),
|
||||||
|
|
||||||
|
chk: &'a mut bool,
|
||||||
|
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> StateHandle<'a,T>
|
||||||
|
{
|
||||||
|
/// The user defined state
|
||||||
|
pub fn state(&self) -> &T
|
||||||
|
{
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
/// The mutable user defined state
|
||||||
|
pub fn state_mut(&mut self) -> &mut T
|
||||||
|
{
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The current args iterator
|
||||||
|
pub fn args(&self) -> &(dyn Iterator<Item=String> + 'a)
|
||||||
|
{
|
||||||
|
self.args
|
||||||
|
}
|
||||||
|
/// The mutable current args iterator
|
||||||
|
pub fn args_mut(&mut self) -> &mut (dyn Iterator<Item = String> + 'a)
|
||||||
|
{
|
||||||
|
self.args
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this callback enabled?
|
||||||
|
///
|
||||||
|
/// It will always start as `true`. This can be mutated to `false` to disable further checks for this argument.
|
||||||
|
pub fn enabled_mut(&mut self) -> &mut bool
|
||||||
|
{
|
||||||
|
self.chk
|
||||||
|
}
|
||||||
|
/// Is this callback enabled?
|
||||||
|
///
|
||||||
|
/// This should always be `true`.
|
||||||
|
pub fn enabled(&mut self) -> bool
|
||||||
|
{
|
||||||
|
*self.chk
|
||||||
|
}
|
||||||
|
/// The index of the current arg
|
||||||
|
pub fn index(&self) -> usize
|
||||||
|
{
|
||||||
|
self.idx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The held values taken from the argument iterator as specified by the argument's definition.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// This moves the values, if you want to call this more than once, use `clone_held`.
|
||||||
|
/// If called more than once will yield no values
|
||||||
|
pub fn extract_held(&mut self) -> EitherIter<String, std::vec::IntoIter<String>>
|
||||||
|
{
|
||||||
|
self.held.take().into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone the held values taken from the argument iterator for this invokation.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// This clones all the values, instead of moving like `extract_held`, so multiple calls to this will yield the same clones.
|
||||||
|
/// However, if `extract_held` has been called, this will yield no values.
|
||||||
|
pub fn clone_held(&self) -> EitherIter<String, std::vec::IntoIter<String>>
|
||||||
|
{
|
||||||
|
self.held.clone().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<U> ArgState<U>
|
||||||
|
{
|
||||||
|
/// Parse this argument iterator.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// This mutates the argument state container indefinately, and multiple calls to it will keep the mutated state.
|
||||||
|
/// So this might not behave as expected (e.g. callbacks marked `single` that were fired in the first call will not be fired in the second, etc.)
|
||||||
|
pub fn parse<I,T: Into<String>>(&mut self, input: I) -> Result<Vec<Box<dyn Any+'static>>, ContextualError>
|
||||||
|
where I: Iterator<Item=T>
|
||||||
|
{
|
||||||
|
let mut output = Vec::with_capacity(self.flags.len());
|
||||||
|
let mut input = input.map(Into::into).fuse();
|
||||||
|
|
||||||
|
let mut i=0;
|
||||||
|
while let Some(arg) = input.next()
|
||||||
|
{
|
||||||
|
for (kind, desc, value, callback, single, chk) in self.flags.iter_mut().filter(|(_,_,_,_,_,chk)| *chk)
|
||||||
|
{
|
||||||
|
if *single {
|
||||||
|
*chk = false;
|
||||||
|
}
|
||||||
|
//TODO: Check Kind matches `arg`, check `value` matches `input` and get the values.
|
||||||
|
output.push(callback(&mut StateHandle{
|
||||||
|
state: &mut self.user,
|
||||||
|
args: &mut input,
|
||||||
|
chk,
|
||||||
|
idx: i,
|
||||||
|
held: Either::None, //TODO: This will be for either one `=<value>` or many `<values...>` from args as according to `value`.
|
||||||
|
}, i, &arg[..])
|
||||||
|
.map_err(|err| Error{flag: kind.clone(), from: err, desc: Some(desc.clone().into_owned()), arg: (i, arg.clone())})?);
|
||||||
|
}
|
||||||
|
i+=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume into the user-provided state value
|
||||||
|
#[inline] pub fn into_inner(self) -> U
|
||||||
|
{
|
||||||
|
self.user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error without context
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error
|
||||||
|
{
|
||||||
|
flag: Kind<'static>,
|
||||||
|
arg: (usize, String),
|
||||||
|
desc: Option<String>,
|
||||||
|
from: eyre::Report,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An argument parsing error with context
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ContextualError(Error);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ErrorContext<'a>
|
||||||
|
{
|
||||||
|
/// The error-throwing flag
|
||||||
|
pub flag: &'a Kind<'static>,
|
||||||
|
/// The argument string
|
||||||
|
pub arg: &'a String,
|
||||||
|
/// The index of the argument string
|
||||||
|
pub arg_idx: &'a usize,
|
||||||
|
/// Message from the failing flag callback, if any
|
||||||
|
pub desc: Option<&'a String>,
|
||||||
|
/// The error report
|
||||||
|
pub from: &'a eyre::Report,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextualError
|
||||||
|
{
|
||||||
|
/// The inner error
|
||||||
|
#[inline] pub fn inner(&self) -> &Error
|
||||||
|
{
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
/// Strips the context and returns the inner error
|
||||||
|
#[inline] fn into_inner(self) -> Error
|
||||||
|
{
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
/// The context of this error
|
||||||
|
pub fn context(&self) -> ErrorContext<'_>
|
||||||
|
{
|
||||||
|
ErrorContext {
|
||||||
|
flag: &self.0.flag,
|
||||||
|
arg: &self.0.arg.1,
|
||||||
|
arg_idx: &self.0.arg.0,
|
||||||
|
desc: self.0.desc.as_ref(),
|
||||||
|
from: &self.0.from
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which argument was the error thrown for
|
||||||
|
pub fn what(&self) -> (usize, &str)
|
||||||
|
{
|
||||||
|
(self.0.arg.0, &self.0.arg.1[..])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where did the parsing fail?
|
||||||
|
pub fn which(&self) -> Kind<'_>
|
||||||
|
{
|
||||||
|
self.0.flag.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The message reported by the failing callback, if any
|
||||||
|
pub fn message(&self) -> Option<&str>
|
||||||
|
{
|
||||||
|
self.0.desc.as_ref().map(|x| &x[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error
|
||||||
|
{
|
||||||
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
|
self.from.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
writeln!(f, "Failed to parse args")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ContextualError
|
||||||
|
{
|
||||||
|
#[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Error
|
||||||
|
{
|
||||||
|
/// Consume into an `eyre::Report`.
|
||||||
|
fn into_report(self) -> eyre::Report
|
||||||
|
{
|
||||||
|
let Error {flag, desc, from ,arg: (idx, arg)} = self;
|
||||||
|
let err = Err::<!, _>(from)
|
||||||
|
//.wrap_err(eyre!("Failed to parse args"))
|
||||||
|
.with_section(move || format!("{:?}", flag).header("While attempting to parse for"))
|
||||||
|
.with_note(move || idx.to_string().header("Argument index was"))
|
||||||
|
.with_note(move || format!("{:?}", arg).header("Argument was"));
|
||||||
|
if let Some(desc) = desc {
|
||||||
|
err.with_suggestion(move || desc)
|
||||||
|
} else {
|
||||||
|
err
|
||||||
|
}.unwrap_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add context to this error
|
||||||
|
pub fn with_context(self) -> ContextualError
|
||||||
|
{
|
||||||
|
ContextualError(Self {
|
||||||
|
flag: self.flag.clone(),
|
||||||
|
arg: self.arg.clone(),
|
||||||
|
desc: self.desc.clone(),
|
||||||
|
from: self.into_report(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for ContextualError
|
||||||
|
{
|
||||||
|
#[inline] fn from(from: Error) -> Self
|
||||||
|
{
|
||||||
|
from.with_context()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ContextualError> for eyre::Report
|
||||||
|
{
|
||||||
|
#[inline] fn from(from: ContextualError) -> Self
|
||||||
|
{
|
||||||
|
from.0.from
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests
|
||||||
|
{
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn errors() -> Result<(), eyre::Report>
|
||||||
|
{
|
||||||
|
//color_eyre::install()?;
|
||||||
|
|
||||||
|
let err= Error {
|
||||||
|
flag: Kind::Long(&["test"], false),
|
||||||
|
arg: (0, "owo".to_owned()),
|
||||||
|
desc: Some("expected between 1-10".to_string()),
|
||||||
|
from: eyre!("Invalid number").wrap_err("Parsing failure"),
|
||||||
|
};
|
||||||
|
println!("No ctx: `{} {:?}`\n", err, err);
|
||||||
|
let err = err.with_context();
|
||||||
|
println!("With ctx: `{} {:?}`\n", err, err);
|
||||||
|
|
||||||
|
//return Err(err)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,275 @@
|
|||||||
|
//! Logging
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
str,
|
||||||
|
fmt,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
use recolored::Colorize;
|
||||||
|
|
||||||
|
/// The logging level
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum Level
|
||||||
|
{
|
||||||
|
Trace,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
Fatal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Level
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "{}", match self {
|
||||||
|
Level::Trace => "TRACE".purple(),
|
||||||
|
Level::Debug => "DEBUG".blue(),
|
||||||
|
Level::Info => "INFO".green(),
|
||||||
|
Level::Warning => "WARNING".yellow(),
|
||||||
|
Level::Error => "ERROR".red(),
|
||||||
|
Level::Fatal => "FATAL".bright_red(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Level
|
||||||
|
{
|
||||||
|
type Err = LevelParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let s = s.trim().to_lowercase();
|
||||||
|
Ok(match s.as_str() {
|
||||||
|
"trace" => Level::Trace,
|
||||||
|
"debug" => Level::Debug,
|
||||||
|
"info" => Level::Info,
|
||||||
|
"warning" => Level::Warning,
|
||||||
|
"error" => Level::Error,
|
||||||
|
"fatal" => Level::Fatal,
|
||||||
|
_ => return Err(LevelParseError(s)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LevelParseError(String);
|
||||||
|
|
||||||
|
impl error::Error for LevelParseError{}
|
||||||
|
impl fmt::Display for LevelParseError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "{:?} is not a valid logging level", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Level
|
||||||
|
{
|
||||||
|
fn is_err(&self) -> bool
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Error |
|
||||||
|
Self::Fatal |
|
||||||
|
Self::Warning => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_print(&self, other: &Self) -> bool
|
||||||
|
{
|
||||||
|
other >= self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Level
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self::Info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains logging state
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Logger<B: ProgressBar = Bar>
|
||||||
|
{
|
||||||
|
level: Level,
|
||||||
|
progress: Handle<B>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ProgressBar> Clone for Logger<B>
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
level: self.level,
|
||||||
|
progress: self.progress.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ProgressBar> Logger<B>
|
||||||
|
{
|
||||||
|
/// Create a new logging state
|
||||||
|
pub fn new(progress: Handle<B>, level: Level) -> Self
|
||||||
|
{
|
||||||
|
Self{progress, level}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference of the progress handle
|
||||||
|
pub fn progress(&self) -> &Handle<B>
|
||||||
|
{
|
||||||
|
&self.progress
|
||||||
|
}
|
||||||
|
/// Get a mutable reference of the progress handle
|
||||||
|
pub fn progress_mut(&mut self) -> &mut Handle<B>
|
||||||
|
{
|
||||||
|
&mut self.progress
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the logging level
|
||||||
|
pub fn level(&self) -> &Level
|
||||||
|
{
|
||||||
|
&self.level
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the logging level
|
||||||
|
pub fn level_mut(&mut self) -> &mut Level
|
||||||
|
{
|
||||||
|
&mut self.level
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_string(&self, level: &Level, disp: impl fmt::Display) -> String
|
||||||
|
{
|
||||||
|
format!("[ {} ]: \t{}", level, disp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn print_display(&mut self, level: Level, disp: impl fmt::Display) -> Result<impl Future<Output=Result<Response, Error>>, Error>
|
||||||
|
{
|
||||||
|
if self.level.should_print(&level) {
|
||||||
|
let string = self.gen_string(&level, disp);
|
||||||
|
|
||||||
|
self.progress.send_command(if level.is_err() {CommandKind::LineErr(string)} else {CommandKind::Line(string)}).await
|
||||||
|
.map_err(Error::Progress)
|
||||||
|
.map(|x| x
|
||||||
|
.map_err(Error::Progress))
|
||||||
|
} else {
|
||||||
|
Err(Error::Level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn print_display_and_wait(&mut self, level: Level, disp: impl fmt::Display) -> Result<Response, Error>
|
||||||
|
{
|
||||||
|
Ok(self.print_display(level, disp).await?.await?)
|
||||||
|
}
|
||||||
|
pub async fn print_display_and_detach(&mut self, level: Level, disp: impl fmt::Display) -> Result<(), Error>
|
||||||
|
{
|
||||||
|
let _ = self.print_display(level, disp).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error
|
||||||
|
{
|
||||||
|
Progress(WorkerCommError),
|
||||||
|
Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error
|
||||||
|
{
|
||||||
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
|
Some(match &self {
|
||||||
|
Self::Progress(p) => p,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Progress(_) => write!(f, "sending to progress worker failed"),
|
||||||
|
Self::Level => write!(f, "invalid level for write"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<WorkerCommError> for Error
|
||||||
|
{
|
||||||
|
#[inline] fn from(from: WorkerCommError) -> Self
|
||||||
|
{
|
||||||
|
Self::Progress(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! trace {
|
||||||
|
(yield $logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_wait($crate::progress::logging::Level::Trace, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_detach($crate::progress::logging::Level::Trace, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! debug {
|
||||||
|
(yield $logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_wait($crate::progress::logging::Level::Debug, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_detach($crate::progress::logging::Level::Debug, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! info {
|
||||||
|
(yield $logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_wait($crate::progress::logging::Level::Info, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_detach($crate::progress::logging::Level::Info, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! warn {
|
||||||
|
(yield $logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_wait($crate::progress::logging::Level::Warn, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_detach($crate::progress::logging::Level::Warn, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! error {
|
||||||
|
(yield $logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_wait($crate::progress::logging::Level::Error, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($logger:expr => $msg:literal $($rest:tt)*) => {
|
||||||
|
{
|
||||||
|
let _ = $logger.print_display_and_detach($crate::progress::logging::Level::Error, ::lazy_format::lazy_format!($msg $($rest)*)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,448 @@
|
|||||||
|
//! Async progression
|
||||||
|
use super::*;
|
||||||
|
use termprogress::{
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use futures::{
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
sync::{
|
||||||
|
mpsc,
|
||||||
|
RwLock,
|
||||||
|
watch,
|
||||||
|
oneshot,
|
||||||
|
|
||||||
|
RwLockReadGuard,
|
||||||
|
RwLockWriteGuard,
|
||||||
|
},
|
||||||
|
task::{self, JoinHandle},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
sync::{
|
||||||
|
Weak,
|
||||||
|
},
|
||||||
|
fmt,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_use] pub mod logging;
|
||||||
|
|
||||||
|
mod tasklist;
|
||||||
|
pub use tasklist::{
|
||||||
|
TaskId,
|
||||||
|
IdNotFoundError,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Command to send to worker task.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CommandKind
|
||||||
|
{
|
||||||
|
Line(String),
|
||||||
|
LineErr(String),
|
||||||
|
|
||||||
|
Bump(isize),
|
||||||
|
BumpHigh(isize),
|
||||||
|
Set{low: Option<usize>, high: Option<usize>},
|
||||||
|
|
||||||
|
Refresh,
|
||||||
|
|
||||||
|
/// Add a task to the tasklist
|
||||||
|
///
|
||||||
|
/// # Response
|
||||||
|
/// Will respond with the task's `TaskId`.
|
||||||
|
AddTask(String),
|
||||||
|
/// Remove a task from the tasklist.
|
||||||
|
///
|
||||||
|
/// # Response
|
||||||
|
/// Will respond with `Result<String, IdNotFoundError>` of the removal operation
|
||||||
|
RemoveTask(TaskId),
|
||||||
|
|
||||||
|
/// Set the title directly
|
||||||
|
SetTitle(String),
|
||||||
|
|
||||||
|
Shutdown,
|
||||||
|
|
||||||
|
Many(Vec<CommandKind>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type sent in response to a `Command`.
|
||||||
|
pub type Response = Option<Box<dyn std::any::Any + Send + 'static>>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum CommandIter
|
||||||
|
{
|
||||||
|
One(std::iter::Once<CommandKind>),
|
||||||
|
Many(std::vec::IntoIter<CommandKind>),
|
||||||
|
}
|
||||||
|
impl ExactSizeIterator for CommandIter{}
|
||||||
|
|
||||||
|
impl Iterator for CommandIter
|
||||||
|
{
|
||||||
|
type Item = CommandKind;
|
||||||
|
fn next(&mut self) -> Option<Self::Item>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::One(one) => one.next(),
|
||||||
|
Self::Many(many) => many.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>)
|
||||||
|
{
|
||||||
|
let sz = match self {
|
||||||
|
Self::One(_) => 1,
|
||||||
|
Self::Many(m) => m.len(),
|
||||||
|
};
|
||||||
|
(sz, Some(sz))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::iter::FusedIterator for CommandIter{}
|
||||||
|
|
||||||
|
|
||||||
|
impl CommandKind
|
||||||
|
{
|
||||||
|
/// Enumerate all possible commands if this is `Many`.
|
||||||
|
///
|
||||||
|
/// The outputs may still contain `Many`.
|
||||||
|
//TODO: Make this work recursively
|
||||||
|
fn enumerate(self) -> CommandIter
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Many(many) => CommandIter::Many(many.into_iter()),
|
||||||
|
other => CommandIter::One(std::iter::once(other)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BarRef<B>(Arc<RwLock<B>>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Command(CommandKind, oneshot::Sender<Response>);
|
||||||
|
|
||||||
|
/// The bar's state
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub struct State
|
||||||
|
{
|
||||||
|
max: usize,
|
||||||
|
cur: usize,
|
||||||
|
//TODO: Tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State
|
||||||
|
{
|
||||||
|
/// The current progress
|
||||||
|
pub fn prog(&self) -> f64
|
||||||
|
{
|
||||||
|
(self.cur as f64) / (self.max as f64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handle to a running async progress bar
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Handle<B = Bar>
|
||||||
|
where B: ProgressBar,
|
||||||
|
{
|
||||||
|
// Channel to send commands to the worker
|
||||||
|
chan: mpsc::Sender<Command>,
|
||||||
|
// A weak reference to the worker's bar itself
|
||||||
|
bar: Weak<RwLock<B>>,
|
||||||
|
// A strong reference to the bar's state
|
||||||
|
state: Arc<RwLock<State>>,
|
||||||
|
// Has the worker shut down?
|
||||||
|
dead: watch::Receiver<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ProgressBar> Clone for Handle<B>
|
||||||
|
{
|
||||||
|
fn clone(&self)->Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
chan: self.chan.clone(),
|
||||||
|
bar: self.bar.clone(),
|
||||||
|
state: self.state.clone(),
|
||||||
|
dead: self.dead.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: ProgressBar> Handle<B>
|
||||||
|
{
|
||||||
|
/// Is the worker alive?
|
||||||
|
pub fn is_alive(&self) -> bool
|
||||||
|
{
|
||||||
|
self.bar.strong_count()>0 && !*self.dead.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Yields until the worker shutds down gracefully
|
||||||
|
pub async fn closed(&mut self) -> Result<(),WorkerCommError>
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
match self.dead.recv().await {
|
||||||
|
Some(true) => return Ok(()),
|
||||||
|
None => return Err(WorkerCommError),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a command to the worker.
|
||||||
|
///
|
||||||
|
/// Returns a future that completes to `Ok` when the worker successfully processes the command, and `Err` if the worker exits before processing it
|
||||||
|
pub async fn send_command(&mut self, command: CommandKind) -> Result<impl Future<Output=Result<Response, WorkerCommError>>, WorkerCommError>
|
||||||
|
{
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.chan.send(Command(command, tx)).await.map_err(|_| WorkerCommError)?;
|
||||||
|
|
||||||
|
Ok(rx.map(|res| res.map_err(|_| WorkerCommError)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a command to the worker and then wait for it to be processed
|
||||||
|
pub async fn send_command_and_wait(&mut self, command: CommandKind) -> Result<Response, WorkerCommError>
|
||||||
|
{
|
||||||
|
self.send_command(command).await?.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a command to the worker and then wait for it to be processed, then attempt to downcast to type `T`.
|
||||||
|
pub async fn send_command_and_downcast<T: 'static>(&mut self, command: CommandKind) -> Result<Option<T>, WorkerCommError>
|
||||||
|
{
|
||||||
|
let resp = self.send_command(command).await?.await?;
|
||||||
|
|
||||||
|
Ok(resp.map(|x| x.downcast::<T>().ok().map(|x| *x)).flatten())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a command to the worker but do not wait for it to be processed
|
||||||
|
pub async fn send_command_and_detach(&mut self, command: CommandKind) -> Result<(), WorkerCommError>
|
||||||
|
{
|
||||||
|
let _ = self.send_command(command).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the state
|
||||||
|
pub async fn state(&self) -> RwLockReadGuard<'_, State>
|
||||||
|
{
|
||||||
|
self.state.read().await
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Act on a mutable reference to the bar within this closure
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// Acquiring this will prevent the worker from exiting until the closure finishes.
|
||||||
|
pub async fn with_bar_mut<F,T>(&self, fun: F) -> Result<T, WorkerCommError>
|
||||||
|
where F: FnOnce(&'_ mut B) -> T,
|
||||||
|
{
|
||||||
|
let handle = self.bar.upgrade().ok_or(WorkerCommError)?;
|
||||||
|
let mut h = handle.write().await;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
Ok(fun(h.deref_mut()))
|
||||||
|
}
|
||||||
|
/// Act on a reference to the bar within this closure
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// Acquiring this will prevent the worker from exiting until the closure finishes.
|
||||||
|
pub async fn with_bar<F,T>(&self, fun: F) -> Result<T, WorkerCommError>
|
||||||
|
where F: FnOnce(&'_ B) -> T,
|
||||||
|
{
|
||||||
|
let handle = self.bar.upgrade().ok_or(WorkerCommError)?;
|
||||||
|
let h = handle.read().await;
|
||||||
|
use std::ops::Deref;
|
||||||
|
Ok(fun(h.deref()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error communicating with worker
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WorkerCommError;
|
||||||
|
|
||||||
|
impl error::Error for WorkerCommError{}
|
||||||
|
impl fmt::Display for WorkerCommError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "failed to communicate with worker.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Host a progress bar and detach it
|
||||||
|
pub fn host<B: ProgressBar + Send + Sync + 'static>(bar: B) -> (Handle<B>, JoinHandle<B>)
|
||||||
|
{
|
||||||
|
let state = Arc::new(RwLock::new(Default::default()));
|
||||||
|
let (mut rx, death, bar, handle) = {
|
||||||
|
let (tx, rx) = mpsc::channel(24);
|
||||||
|
let (death, dead) = watch::channel(false);
|
||||||
|
let bar = Arc::new(RwLock::new(bar));
|
||||||
|
let handle = Handle {
|
||||||
|
chan: tx,
|
||||||
|
dead,
|
||||||
|
bar: Arc::downgrade(&bar),
|
||||||
|
state: Arc::clone(&state),
|
||||||
|
};
|
||||||
|
(rx,death,bar,handle)
|
||||||
|
};
|
||||||
|
(handle, tokio::spawn(async move {
|
||||||
|
({
|
||||||
|
let mut tasks = tasklist::TaskList::new();
|
||||||
|
macro_rules! update_bar {
|
||||||
|
(refresh $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let bar = bar.read().await;
|
||||||
|
bar.refresh();
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(to $state:ident $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let mut bar = bar.write().await;
|
||||||
|
bar.set_progress($state.prog());
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(write error $line:ident $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let bar = bar.read().await;
|
||||||
|
let string = &$line[..];
|
||||||
|
bar.eprintln(string);
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(write $(std)? $line:ident $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let bar = bar.read().await;
|
||||||
|
let string = &$line[..];
|
||||||
|
bar.println(string);
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(title $(std)? $title:ident $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let mut bar = bar.write().await;
|
||||||
|
bar.set_title(&$title[..]);
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(title $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let mut bar = bar.write().await;
|
||||||
|
bar.set_title(tasks.as_str());
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(+task $task:ident $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let id = tasks.add($task);
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(-task $id:ident $($tt:tt)*) => {
|
||||||
|
{
|
||||||
|
let res = tasks.remove(&$id);
|
||||||
|
update_bar!($($tt)*);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
};
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
update_bar!(refresh);
|
||||||
|
while let Some(Command(command, response)) = rx.recv().await {
|
||||||
|
let response = Arc::new(std::sync::Mutex::new(Some(response)));
|
||||||
|
|
||||||
|
/// Send a response if one has not already been sent.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Some(Ok(())` - if response was sent okay
|
||||||
|
/// * `Some(Err(_))` - if response failed to send.
|
||||||
|
/// * `None` - if response has already been sent
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If mutex is poisoned (this should be impossible).
|
||||||
|
macro_rules! send_response {
|
||||||
|
($value:expr) => (send_response!(@ response => Some(Box::new($value))));
|
||||||
|
(@ $response:ident => $value:expr) => {
|
||||||
|
{
|
||||||
|
let value: Response = $value;
|
||||||
|
{
|
||||||
|
if let Some(response) = $response.lock().unwrap().take() {
|
||||||
|
Some(response.send(value))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard that ensures a `None` response is sent after this command has been processed, if an explicit response has not yet been sent.
|
||||||
|
let _resp = {
|
||||||
|
let response = Arc::clone(&response);
|
||||||
|
util::defer(move || send_response!(@ response => None).ignore())
|
||||||
|
};
|
||||||
|
match command {
|
||||||
|
CommandKind::Shutdown => break,
|
||||||
|
CommandKind::BumpHigh(sz) if sz >= 0 => {
|
||||||
|
let mut state = state.write().await;
|
||||||
|
state.max = state.max.saturating_add(sz as usize);
|
||||||
|
|
||||||
|
update_bar!(to state);
|
||||||
|
},
|
||||||
|
CommandKind::BumpHigh(sz) => {
|
||||||
|
debug_assert!(sz <0);
|
||||||
|
let mut state = state.write().await;
|
||||||
|
state.max = state.max.saturating_sub(sz.abs() as usize);
|
||||||
|
|
||||||
|
update_bar!(to state);
|
||||||
|
},
|
||||||
|
CommandKind::Bump(sz) if sz >= 0 => {
|
||||||
|
let mut state = state.write().await;
|
||||||
|
state.cur = state.cur.saturating_add(sz as usize);
|
||||||
|
|
||||||
|
update_bar!(to state);
|
||||||
|
},
|
||||||
|
CommandKind::Bump(sz) => {
|
||||||
|
debug_assert!(sz <0);
|
||||||
|
let mut state = state.write().await;
|
||||||
|
state.cur = state.cur.saturating_sub(sz.abs() as usize);
|
||||||
|
|
||||||
|
update_bar!(to state);
|
||||||
|
},
|
||||||
|
CommandKind::Set{low: None, high: None} => (),
|
||||||
|
CommandKind::Set{low, high} => {
|
||||||
|
let mut state = state.write().await;
|
||||||
|
state.cur = low.unwrap_or(state.cur);
|
||||||
|
state.max = high.unwrap_or(state.max);
|
||||||
|
|
||||||
|
update_bar!(to state);
|
||||||
|
},
|
||||||
|
CommandKind::Line(line) => update_bar!(write line),
|
||||||
|
CommandKind::LineErr(line) => update_bar!(write error line),
|
||||||
|
|
||||||
|
CommandKind::AddTask(string) => {
|
||||||
|
send_response!(update_bar!(+task string title));
|
||||||
|
},
|
||||||
|
CommandKind::RemoveTask(id) => {
|
||||||
|
send_response!(update_bar!(-task id title));
|
||||||
|
},
|
||||||
|
CommandKind::SetTitle(string) => update_bar!(title string),
|
||||||
|
CommandKind::Refresh => update_bar!(refresh),
|
||||||
|
CommandKind::Many(_) => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the bar and return
|
||||||
|
{
|
||||||
|
let mut bar = bar;
|
||||||
|
loop {
|
||||||
|
bar = match Arc::try_unwrap(bar) {
|
||||||
|
Ok(bar) => break bar,
|
||||||
|
Err(bar) => bar,
|
||||||
|
};
|
||||||
|
task::yield_now().await;
|
||||||
|
}.into_inner()
|
||||||
|
}
|
||||||
|
}, death.broadcast(true)).0
|
||||||
|
}))
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
//! Tasklist for progressbar
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
collections::LinkedList,
|
||||||
|
fmt,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct TaskId(Uuid);
|
||||||
|
|
||||||
|
impl fmt::Display for TaskId
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "<{}>", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A list of tasks
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TaskList
|
||||||
|
{
|
||||||
|
tasks: LinkedList<(Uuid, String)>,
|
||||||
|
|
||||||
|
strbuf: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TaskList
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "{}", self.strbuf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskList
|
||||||
|
{
|
||||||
|
/// The current full string
|
||||||
|
pub fn as_str(&self) -> &str
|
||||||
|
{
|
||||||
|
self.strbuf.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new, empty tasklist
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self{tasks:LinkedList::new(), strbuf:String::new()}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recalc_buf(&mut self)
|
||||||
|
{
|
||||||
|
self.strbuf = self.tasks.iter().map(|(_, stri)| stri.as_str()).join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_buf_one(&mut self, task: &str)
|
||||||
|
{
|
||||||
|
if self.strbuf.len() > 0 {
|
||||||
|
self.strbuf.push_str(", ");
|
||||||
|
}
|
||||||
|
self.strbuf.push_str(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a task to the end of the list
|
||||||
|
pub fn add(&mut self, task: impl Into<String>) -> TaskId
|
||||||
|
{
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let task = task.into();
|
||||||
|
self.push_buf_one(&task[..]);
|
||||||
|
self.tasks.push_back((id.clone(), task));
|
||||||
|
|
||||||
|
TaskId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all tasks
|
||||||
|
pub fn clear(&mut self)
|
||||||
|
{
|
||||||
|
self.tasks.clear();
|
||||||
|
self.strbuf.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all tasks currently in
|
||||||
|
pub fn tasks(&self) -> impl Iterator<Item = &'_ str> + '_
|
||||||
|
{
|
||||||
|
self.tasks.iter().map(|(_, strs)| strs.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove this task from the list, returning its string if it exists
|
||||||
|
pub fn remove(&mut self, task_id: &TaskId) -> Result<String, IdNotFoundError>
|
||||||
|
{
|
||||||
|
let value = match self.tasks.drain_filter(|(id, _)| id==&task_id.0).next() {
|
||||||
|
Some((_, string)) => string,
|
||||||
|
None => return Err(IdNotFoundError(TaskId(task_id.0.clone()))),
|
||||||
|
};
|
||||||
|
self.recalc_buf();
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this task ID's string
|
||||||
|
pub fn task_get(&self, task_id: &TaskId)-> Option<&str>
|
||||||
|
{
|
||||||
|
self.tasks.iter().filter(|(id, _)| id == &task_id.0).next().map(|x| x.1.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace this task ID with this string, retuning the old one.
|
||||||
|
pub fn task_set(&mut self, task_id: &TaskId, value: impl Into<String>) -> Result<String, IdNotFoundError>
|
||||||
|
{
|
||||||
|
let old = match self.tasks.iter_mut().filter(|(id, _)| id == &task_id.0).next().map(|x| &mut x.1) {
|
||||||
|
Some(string) => std::mem::replace(string, value.into()),
|
||||||
|
None => return Err(IdNotFoundError(TaskId(task_id.0.clone()))),
|
||||||
|
};
|
||||||
|
self.recalc_buf();
|
||||||
|
Ok(old)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error when trying to remove a non-existent ID.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IdNotFoundError(TaskId);
|
||||||
|
|
||||||
|
impl IdNotFoundError
|
||||||
|
{
|
||||||
|
/// Get the ID that was not found
|
||||||
|
pub fn id(&self) -> &TaskId
|
||||||
|
{
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for IdNotFoundError{}
|
||||||
|
impl fmt::Display for IdNotFoundError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "{}: unknown ID to this TaskList", self.0)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue