use std::ops::{Deref, DerefMut};
use anyhow::bail;
use serde::{Deserialize, Serialize};
use crate::{
lang::translate::TranslationKey,
pkg::{PackageDiff, PkgRequest, ResolutionError},
};
#[async_trait::async_trait]
pub trait NitroOutput: Send {
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) {
self.display_message(Message {
contents,
level: MessageLevel::Important,
})
}
fn debug(&mut self, contents: MessageContents) {
self.display_message(Message {
contents,
level: MessageLevel::Debug,
})
}
fn trace(&mut self, contents: MessageContents) {
self.display_message(Message {
contents,
level: MessageLevel::Trace,
})
}
fn start_process(&mut self) {}
fn end_process(&mut self) {}
fn start_section(&mut self) {}
fn end_section(&mut self) {}
fn get_process(&'_ mut self) -> OutputProcess<'_, Self>
where
Self: Sized,
{
OutputProcess::new(self)
}
fn get_section(&'_ mut self) -> OutputSection<'_, Self>
where
Self: Sized,
{
OutputSection::new(self)
}
async fn prompt_yes_no(
&mut self,
default: bool,
message: MessageContents,
) -> anyhow::Result<bool> {
let _message = message;
Ok(default)
}
async fn prompt_password(&mut self, message: MessageContents) -> anyhow::Result<String> {
let _ = message;
bail!("No password prompt available")
}
async fn prompt_new_password(&mut self, message: MessageContents) -> anyhow::Result<String> {
self.prompt_password(message).await
}
fn translate(&self, key: TranslationKey) -> &str {
key.get_default()
}
fn display_special_ms_auth(&mut self, url: &str, code: &str) {
default_special_ms_auth(self, url, code);
}
fn display_special_resolution_error(&mut self, error: ResolutionError, instance_id: &str) {
self.display(MessageContents::Error(format!(
"Failed to resolve packages for instance {instance_id}: {error}"
)))
}
async fn prompt_special_account_passkey(
&mut self,
message: MessageContents,
account_id: &str,
) -> anyhow::Result<String> {
let _ = account_id;
self.prompt_password(message).await
}
async fn prompt_special_package_diffs(
&mut self,
diffs: Vec<PackageDiff>,
) -> anyhow::Result<bool> {
self.display(MessageContents::Header("Package changes:".into()));
for diff in diffs {
match &diff {
PackageDiff::Added(pkg)
| PackageDiff::Removed(pkg)
| PackageDiff::VersionChanged(pkg, ..) => {
let pkg = PkgRequest::clone(pkg);
let message = match &diff {
PackageDiff::Added(..) => "Added".to_string(),
PackageDiff::Removed(..) => "Removed".to_string(),
PackageDiff::VersionChanged(_, old_version, new_version) => {
format!("{old_version} -> {new_version}")
}
_ => unreachable!(),
};
self.display(MessageContents::Package(
pkg,
Box::new(MessageContents::Simple(message)),
));
}
PackageDiff::ManyAdded(count) => {
self.display(MessageContents::Simple(format!("Added {count} packages")))
}
PackageDiff::ManyRemoved(count) => {
self.display(MessageContents::Simple(format!("Removed {count} packages")))
}
}
}
self.prompt_yes_no(
false,
MessageContents::Simple("Would you like to proceed with these changes?".into()),
)
.await
}
fn get_greater_copy(&self) -> Box<dyn NitroOutput + Sync> {
self.get_lesser_copy()
}
fn get_lesser_copy(&self) -> Box<dyn NitroOutput + Sync> {
Box::new(NoOp)
}
}
#[async_trait::async_trait]
impl<T: NitroOutput + Sync + ?Sized> NitroOutput for Box<T> {
fn display_text(&mut self, text: String, level: MessageLevel) {
self.deref_mut().display_text(text, level)
}
fn display_message(&mut self, message: Message) {
self.deref_mut().display_message(message)
}
fn start_process(&mut self) {
self.deref_mut().start_process()
}
fn end_process(&mut self) {
self.deref_mut().end_process()
}
fn start_section(&mut self) {
self.deref_mut().start_section()
}
fn end_section(&mut self) {
self.deref_mut().end_section()
}
async fn prompt_yes_no(
&mut self,
default: bool,
message: MessageContents,
) -> anyhow::Result<bool> {
self.deref_mut().prompt_yes_no(default, message).await
}
async fn prompt_password(&mut self, message: MessageContents) -> anyhow::Result<String> {
self.deref_mut().prompt_password(message).await
}
async fn prompt_new_password(&mut self, message: MessageContents) -> anyhow::Result<String> {
self.deref_mut().prompt_new_password(message).await
}
fn translate(&self, key: TranslationKey) -> &str {
self.deref().translate(key)
}
fn display_special_ms_auth(&mut self, url: &str, code: &str) {
self.deref_mut().display_special_ms_auth(url, code)
}
fn display_special_resolution_error(&mut self, error: ResolutionError, instance_id: &str) {
self.deref_mut()
.display_special_resolution_error(error, instance_id)
}
async fn prompt_special_account_passkey(
&mut self,
message: MessageContents,
account_id: &str,
) -> anyhow::Result<String> {
self.deref_mut()
.prompt_special_account_passkey(message, account_id)
.await
}
async fn prompt_special_package_diffs(
&mut self,
diffs: Vec<PackageDiff>,
) -> anyhow::Result<bool> {
self.deref_mut().prompt_special_package_diffs(diffs).await
}
fn get_greater_copy(&self) -> Box<dyn NitroOutput + Sync> {
self.deref().get_lesser_copy()
}
fn get_lesser_copy(&self) -> Box<dyn NitroOutput + Sync> {
self.deref().get_lesser_copy()
}
}
pub fn default_special_ms_auth(o: &mut (impl NitroOutput + ?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())),
));
o.display(MessageContents::Property(
"and enter the code".into(),
Box::new(MessageContents::Copyable(code.into())),
));
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Message {
pub contents: MessageContents,
pub level: MessageLevel,
}
#[non_exhaustive]
#[derive(Clone, Debug, Deserialize, Serialize)]
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, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum MessageLevel {
Trace,
Debug,
Important,
}
#[derive(Clone, Copy)]
pub struct NoOp;
impl NitroOutput for NoOp {
fn display_text(&mut self, _text: String, _level: MessageLevel) {}
}
#[derive(Clone, Copy)]
pub struct Simple(pub MessageLevel);
impl NitroOutput for Simple {
fn display_text(&mut self, text: String, level: MessageLevel) {
if level < self.0 {
return;
}
println!("{text}");
}
fn get_lesser_copy(&self) -> Box<dyn NitroOutput + Sync> {
Box::new(Self(self.0))
}
}
#[derive(Clone)]
pub struct TestOutput(pub Vec<Message>);
impl NitroOutput for TestOutput {
fn display_text(&mut self, _text: String, _level: MessageLevel) {}
fn display_message(&mut self, message: Message) {
self.0.push(message);
}
}
pub struct OutputProcess<'a, O: NitroOutput>(&'a mut O);
impl<'a, O> OutputProcess<'a, O>
where
O: NitroOutput,
{
pub fn new(o: &'a mut O) -> Self {
o.start_process();
Self(o)
}
pub fn finish(self) {}
}
impl<O> Drop for OutputProcess<'_, O>
where
O: NitroOutput,
{
fn drop(&mut self) {
self.0.end_process();
}
}
impl<O> Deref for OutputProcess<'_, O>
where
O: NitroOutput,
{
type Target = O;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<O> DerefMut for OutputProcess<'_, O>
where
O: NitroOutput,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
}
}
pub struct OutputSection<'a, O: NitroOutput>(&'a mut O);
impl<'a, O> OutputSection<'a, O>
where
O: NitroOutput,
{
pub fn new(o: &'a mut O) -> Self {
o.start_section();
Self(o)
}
pub fn finish(self) {}
}
impl<O> Drop for OutputSection<'_, O>
where
O: NitroOutput,
{
fn drop(&mut self) {
self.0.end_section();
}
}
impl<O> Deref for OutputSection<'_, O>
where
O: NitroOutput,
{
type Target = O;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<O> DerefMut for OutputSection<'_, O>
where
O: NitroOutput,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
}
}