use crate::pkg::PkgRequest;
pub trait MCVMOutput {
fn display_text(&mut self, text: String, level: MessageLevel);
fn display_message(&mut self, message: Message) {
self.display_text(message.contents.default_format(), message.level);
}
fn display(&mut self, contents: MessageContents, level: MessageLevel) {
self.display_message(Message { contents, level })
}
fn start_process(&mut self) {}
fn end_process(&mut self) {}
fn start_section(&mut self) {}
fn end_section(&mut self) {}
fn prompt_yes_no(&mut self, default: bool, message: MessageContents) -> anyhow::Result<bool> {
let _message = message;
Ok(default)
}
fn display_special_ms_auth(&mut self, url: &str, code: &str) {
default_special_ms_auth(self, url, code);
}
}
pub fn default_special_ms_auth(o: &mut (impl MCVMOutput + ?Sized), url: &str, code: &str) {
o.display(
MessageContents::Property(
"Open this link in your web browser if it has not opened already".into(),
Box::new(MessageContents::Hyperlink(url.into())),
),
MessageLevel::Important,
);
o.display(
MessageContents::Property(
"and enter the code".into(),
Box::new(MessageContents::Copyable(code.into())),
),
MessageLevel::Important,
);
}
#[derive(Clone, Debug)]
pub struct Message {
pub contents: MessageContents,
pub level: MessageLevel,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum MessageContents {
Simple(String),
Notice(String),
Warning(String),
Error(String),
Success(String),
Property(String, Box<MessageContents>),
Header(String),
StartProcess(String),
Associated(Box<MessageContents>, Box<MessageContents>),
Package(PkgRequest, Box<MessageContents>),
Hyperlink(String),
ListItem(Box<MessageContents>),
Copyable(String),
Progress {
current: u32,
total: u32,
},
}
impl MessageContents {
pub fn default_format(self) -> String {
match self {
MessageContents::Simple(text)
| MessageContents::Success(text)
| MessageContents::Hyperlink(text)
| MessageContents::Copyable(text) => text,
MessageContents::Notice(text) => format!("Notice: {text}"),
MessageContents::Warning(text) => format!("Warning: {text}"),
MessageContents::Error(text) => format!("Error: {text}"),
MessageContents::Property(key, value) => {
format!("{key}: {}", value.default_format())
}
MessageContents::Header(text) => text.to_uppercase(),
MessageContents::StartProcess(text) => format!("{text}..."),
MessageContents::Associated(item, message) => {
format!("[{}] {}", item.default_format(), message.default_format())
}
MessageContents::Package(pkg, message) => {
format!("[{pkg}] {}", message.default_format())
}
MessageContents::ListItem(item) => format!(" - {}", item.default_format()),
MessageContents::Progress { current, total } => format!("{current}/{total}"),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum MessageLevel {
Important,
Extra,
Debug,
Trace,
}
impl MessageLevel {
pub fn at_least(&self, other: &Self) -> bool {
match &self {
Self::Important => matches!(
other,
Self::Important | Self::Extra | Self::Debug | Self::Trace
),
Self::Extra => matches!(other, Self::Extra | Self::Debug | Self::Trace),
Self::Debug => matches!(other, Self::Debug | Self::Trace),
Self::Trace => matches!(other, Self::Trace),
}
}
}
pub struct NoOp;
impl MCVMOutput for NoOp {
fn display_text(&mut self, _text: String, _level: MessageLevel) {}
}
pub struct Simple(pub MessageLevel);
impl MCVMOutput for Simple {
fn display_text(&mut self, text: String, level: MessageLevel) {
if !level.at_least(&self.0) {
return;
}
println!("{text}");
}
}
pub struct OutputProcess<'a, O: MCVMOutput>(pub &'a mut O);
impl<'a, O> OutputProcess<'a, O>
where
O: MCVMOutput,
{
pub fn new(o: &'a mut O) -> Self {
o.start_process();
Self(o)
}
}
impl<'a, O> Drop for OutputProcess<'a, O>
where
O: MCVMOutput,
{
fn drop(&mut self) {
self.0.end_process();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_level_is_at_least() {
assert!(MessageLevel::Extra.at_least(&MessageLevel::Debug));
assert!(MessageLevel::Debug.at_least(&MessageLevel::Debug));
assert!(!MessageLevel::Debug.at_least(&MessageLevel::Extra));
}
}