use crate::config::Settings;
use crate::task::Task;
use crate::task::task_helpers::task_needs_permit;
use crate::task::task_output::TaskOutput;
use crate::ui::multi_progress_report::MultiProgressReport;
use crate::ui::progress_report::SingleReport;
use indexmap::IndexMap;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
type TaskPrMap = Arc<Mutex<IndexMap<Task, Arc<Box<dyn SingleReport>>>>>;
pub enum KeepOrderLine {
Stdout(String, String), Stderr(String, String), }
pub struct KeepOrderState {
active: Option<Task>,
buffers: IndexMap<Task, Vec<KeepOrderLine>>,
finished: Vec<Task>,
done: bool,
}
impl KeepOrderState {
pub fn new() -> Self {
Self {
active: None,
buffers: IndexMap::new(),
finished: Vec::new(),
done: false,
}
}
pub fn init_task(&mut self, task: &Task) {
self.buffers.entry(task.clone()).or_default();
}
fn is_active(&self, task: &Task) -> bool {
if let Some(active) = &self.active {
active == task
} else {
self.buffers.first().map(|(t, _)| t) == Some(task)
}
}
pub fn on_stdout(&mut self, task: &Task, prefix: String, line: String) {
if self.done || self.is_active(task) {
self.active = Some(task.clone());
print_stdout(&prefix, &line);
} else {
self.buffers
.entry(task.clone())
.or_default()
.push(KeepOrderLine::Stdout(prefix, line));
}
}
pub fn on_stderr(&mut self, task: &Task, prefix: String, line: String) {
if self.done || self.is_active(task) {
self.active = Some(task.clone());
print_stderr(&prefix, &line);
} else {
self.buffers
.entry(task.clone())
.or_default()
.push(KeepOrderLine::Stderr(prefix, line));
}
}
pub fn on_task_finished(&mut self, task: &Task) {
if !self.buffers.contains_key(task) {
return; }
if self.is_active(task) {
self.active = None;
self.buffers.shift_remove(task);
self.flush_finished();
self.promote_next();
} else {
self.finished.push(task.clone());
}
}
fn flush_finished(&mut self) {
let mut finished: std::collections::HashSet<_> = self.finished.drain(..).collect();
while let Some((task, _)) = self.buffers.first() {
if !finished.remove(task) {
break; }
let task = task.clone();
if let Some(lines) = self.buffers.shift_remove(&task) {
Self::print_lines(&lines);
}
}
self.finished.extend(finished);
}
fn promote_next(&mut self) {
if let Some((task, _)) = self.buffers.first() {
let task = task.clone();
self.active = Some(task.clone());
if let Some(lines) = self.buffers.get_mut(&task) {
let lines = std::mem::take(lines);
Self::print_lines(&lines);
}
}
}
fn print_lines(lines: &[KeepOrderLine]) {
for line in lines {
match line {
KeepOrderLine::Stdout(prefix, line) => print_stdout(prefix, line),
KeepOrderLine::Stderr(prefix, line) => print_stderr(prefix, line),
}
}
}
pub fn flush_all(&mut self) {
self.active = None;
self.flush_finished();
for (_, lines) in self.buffers.drain(..) {
Self::print_lines(&lines);
}
self.done = true;
}
}
fn print_stdout(prefix: &str, line: &str) {
if console::colors_enabled() {
prefix_println!(prefix, "{line}\x1b[0m");
} else {
prefix_println!(prefix, "{line}");
}
}
fn print_stderr(prefix: &str, line: &str) {
if console::colors_enabled_stderr() {
prefix_eprintln!(prefix, "{line}\x1b[0m");
} else {
prefix_eprintln!(prefix, "{line}");
}
}
pub struct OutputHandlerConfig {
pub output: Option<TaskOutput>,
pub silent: bool,
pub quiet: bool,
pub raw: bool,
pub is_linear: bool,
pub jobs: Option<usize>,
}
pub struct OutputHandler {
pub keep_order_state: Arc<Mutex<KeepOrderState>>,
pub task_prs: TaskPrMap,
pub timed_outputs: Arc<Mutex<IndexMap<String, (SystemTime, String)>>>,
output: Option<TaskOutput>,
silent: bool,
quiet: bool,
raw: bool,
is_linear: bool,
jobs: Option<usize>,
}
impl Clone for OutputHandler {
fn clone(&self) -> Self {
Self {
keep_order_state: self.keep_order_state.clone(),
task_prs: self.task_prs.clone(),
timed_outputs: self.timed_outputs.clone(),
output: self.output,
silent: self.silent,
quiet: self.quiet,
raw: self.raw,
is_linear: self.is_linear,
jobs: self.jobs,
}
}
}
impl OutputHandler {
pub fn get_or_init_task_pr(&self, task: &Task) -> Arc<Box<dyn SingleReport>> {
let mut prs = self.task_prs.lock().unwrap();
if let Some(pr) = prs.get(task) {
pr.clone()
} else {
let pr = MultiProgressReport::get().add(&task.estyled_prefix());
let pr = Arc::new(pr);
prs.insert(task.clone(), pr.clone());
pr
}
}
}
impl OutputHandler {
pub fn new(config: OutputHandlerConfig) -> Self {
Self {
keep_order_state: Arc::new(Mutex::new(KeepOrderState::new())),
task_prs: Arc::new(Mutex::new(IndexMap::new())),
timed_outputs: Arc::new(Mutex::new(IndexMap::new())),
output: config.output,
silent: config.silent,
quiet: config.quiet,
raw: config.raw,
is_linear: config.is_linear,
jobs: config.jobs,
}
}
pub fn init_task(&mut self, task: &Task) {
match self.output(Some(task)) {
TaskOutput::KeepOrder if task_needs_permit(task) => {
self.keep_order_state.lock().unwrap().init_task(task);
}
TaskOutput::Replacing => {
self.get_or_init_task_pr(task);
}
_ => {}
}
}
pub fn output(&self, task: Option<&Task>) -> TaskOutput {
if let Some(task_ref) = task
&& matches!(task_ref.silent, crate::task::Silent::Bool(true))
{
return TaskOutput::Silent;
}
if let Some(o) = self.output {
return o;
} else if let Some(task_ref) = task {
if self.silent_bool() {
return TaskOutput::Silent;
}
if self.quiet(Some(task_ref)) {
return TaskOutput::Quiet;
}
} else if self.silent_bool() {
return TaskOutput::Silent;
} else if self.quiet(task) {
return TaskOutput::Quiet;
}
if let Some(output) = Settings::get().task.output {
if output.is_silent() || output.is_quiet() {
output
} else if self.raw(task) {
TaskOutput::Interleave
} else {
output
}
} else if self.raw(task) || self.jobs() == 1 || self.is_linear {
TaskOutput::Interleave
} else {
TaskOutput::Prefix
}
}
pub fn eprint(&self, task: &Task, prefix: &str, line: &str) {
match self.output(Some(task)) {
TaskOutput::KeepOrder => {
self.keep_order_state.lock().unwrap().on_stderr(
task,
prefix.to_string(),
line.to_string(),
);
}
TaskOutput::Replacing => {
let pr = self.get_or_init_task_pr(task);
pr.set_message(format!("{prefix} {line}"));
}
_ => {
prefix_eprintln!(prefix, "{line}");
}
}
}
fn silent_bool(&self) -> bool {
self.silent
|| Settings::get().silent
|| self.output.is_some_and(|o| o.is_silent())
|| Settings::get().task.output.is_some_and(|o| o.is_silent())
}
pub fn silent(&self, task: Option<&Task>) -> bool {
self.silent_bool() || task.is_some_and(|t| t.silent.is_silent())
}
pub fn quiet(&self, task: Option<&Task>) -> bool {
self.quiet
|| Settings::get().quiet
|| self.output.is_some_and(|o| o.is_quiet())
|| Settings::get().task.output.is_some_and(|o| o.is_quiet())
|| task.is_some_and(|t| t.quiet)
|| self.silent(task)
}
pub fn raw(&self, task: Option<&Task>) -> bool {
self.raw || Settings::get().raw || task.is_some_and(|t| t.raw || t.interactive)
}
pub fn jobs(&self) -> usize {
if self.raw {
1
} else {
self.jobs.unwrap_or(Settings::get().jobs)
}
}
}