use crate::{Result, file_rw_locks::Flocks, hook::SkipReason, step::RunType};
use clx::progress::{ProgressJob, ProgressJobBuilder, ProgressJobDoneBehavior, ProgressStatus};
use itertools::Itertools;
use tokio::sync::OwnedSemaphorePermit;
use crate::{env, step::Step, step_context::StepContext, step_locks::StepLocks, tera};
use std::{path::PathBuf, sync::Arc};
#[derive(Debug)]
pub struct StepJob {
pub step: Arc<Step>,
pub files: Vec<PathBuf>,
pub run_type: RunType,
pub check_first: bool,
pub skip_reason: Option<SkipReason>,
pub progress: Option<Arc<ProgressJob>>,
pub semaphore: Option<OwnedSemaphorePermit>,
workspace_indicator: Option<PathBuf>,
pub status: StepJobStatus,
}
#[derive(Debug, strum::EnumIs, strum::Display)]
pub enum StepJobStatus {
Pending,
Started(StepLocks),
Finished,
Errored(String),
}
impl StepJob {
pub fn new(step: Arc<Step>, files: Vec<PathBuf>, run_type: RunType) -> Self {
Self {
files,
run_type,
workspace_indicator: None,
check_first: *env::HK_CHECK_FIRST
&& step.check_first
&& step.fix.is_some()
&& (step.check.is_some()
|| step.check_diff.is_some()
|| step.check_list_files.is_some())
&& matches!(run_type, RunType::Fix),
step,
status: StepJobStatus::Pending,
skip_reason: None,
progress: None,
semaphore: None,
}
}
pub fn with_workspace_indicator(mut self, workspace_indicator: PathBuf) -> Self {
self.workspace_indicator = Some(workspace_indicator);
self
}
pub fn tctx(&self, base: &tera::Context) -> tera::Context {
let mut tctx = base.clone();
tctx.insert("step", &self.step.name);
let command_files = if let Some(dir) = &self.step.dir {
self.files
.iter()
.map(|f| f.strip_prefix(dir).unwrap_or(f).to_path_buf())
.collect::<Vec<_>>()
} else {
self.files.clone()
};
tctx.with_files(self.step.shell_type(), &command_files);
if let Some(workspace_indicator) = &self.workspace_indicator {
tctx.with_workspace_indicator(workspace_indicator);
let workspace_dir = workspace_indicator
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or(std::path::Path::new("."));
tctx.with_workspace_files(self.step.shell_type(), workspace_dir, &self.files);
}
tctx
}
pub fn build_progress(&self, ctx: &StepContext) -> Arc<ProgressJob> {
let job = ProgressJobBuilder::new()
.prop("name", &self.step.name)
.prop("files", &self.files.iter().map(|f| f.display()).join(" "))
.body(
"{{spinner()}} {% if ensembler_cmd %}{{ensembler_cmd | flex}}{% if ensembler_stdout %}\n{{ensembler_stdout | flex}}{% endif %}{% else %}{{message | flex}}{% endif %}"
)
.body_text(Some(
"{% if ensembler_stdout %} {{name}} – {{ensembler_stdout | truncate_text}}{% elif message %}{{spinner()}} {{name}} – {{message | truncate_text}}{% endif %}".to_string(),
))
.status(ProgressStatus::Hide)
.on_done(ProgressJobDoneBehavior::Hide)
.build();
ctx.progress.add(job)
}
pub async fn status_start(
&mut self,
ctx: &StepContext,
semaphore: OwnedSemaphorePermit,
) -> Result<()> {
match &self.status {
StepJobStatus::Pending => {}
StepJobStatus::Started(_) => {
return Ok(());
}
_ => unreachable!("invalid status: {:?}", self.status),
}
let flocks = self.flocks(ctx).await;
self.status = StepJobStatus::Started(StepLocks::new(flocks, semaphore));
ctx.status_started();
if let Some(progress) = &mut self.progress {
progress.set_status(ProgressStatus::Running);
}
Ok(())
}
pub fn status_finished(&mut self) -> Result<()> {
match &mut self.status {
StepJobStatus::Started(_) => {}
_ => unreachable!("invalid status: {:?}", self.status),
}
self.status = StepJobStatus::Finished;
if let Some(progress) = &mut self.progress {
progress.set_status(ProgressStatus::Done);
}
Ok(())
}
pub async fn status_errored(&mut self, ctx: &StepContext, err: String) -> Result<()> {
match &mut self.status {
StepJobStatus::Pending | StepJobStatus::Started(_) => {}
_ => unreachable!("invalid status: {:?}", self.status),
}
self.status = StepJobStatus::Errored(err.to_string());
if let Some(progress) = &mut self.progress {
progress.prop("message", &err);
progress.set_status(ProgressStatus::Failed);
}
ctx.status_errored(&err);
Ok(())
}
async fn flocks(&self, ctx: &StepContext) -> Flocks {
if self.step.stomp {
Default::default()
} else if self.run_type == RunType::Fix {
ctx.hook_ctx.file_locks.write_locks(&self.files).await
} else {
ctx.hook_ctx.file_locks.read_locks(&self.files).await
}
}
}
impl Clone for StepJob {
fn clone(&self) -> Self {
Self {
step: self.step.clone(),
files: self.files.clone(),
run_type: self.run_type,
check_first: self.check_first,
skip_reason: self.skip_reason.clone(),
workspace_indicator: self.workspace_indicator.clone(),
status: StepJobStatus::Pending,
progress: self.progress.clone(),
semaphore: None,
}
}
}