use crate::*;
use carryctx::application;
use carryctx::application::runtime::InvocationContext;
use carryctx::domain::progress::ProgressType;
use carryctx::error::ExitCode;
use clap::Parser;
#[derive(Parser, Debug)]
pub enum ProgressCommand {
Todo {
content: String,
#[arg(long)]
task: Option<String>,
},
Block {
content: String,
#[arg(long)]
task: Option<String>,
},
Risk {
content: String,
#[arg(long)]
task: Option<String>,
},
Note {
content: String,
#[arg(long)]
task: Option<String>,
},
List {
#[arg(long)]
task: Option<String>,
},
Show { progress_ref: String },
Edit {
progress_ref: String,
#[arg(long)]
content: String,
},
Complete { progress_ref: String },
Reopen { progress_ref: String },
Remove { progress_ref: String },
Reorder {
#[arg(long)]
task: String,
#[arg(long)]
order: Vec<String>,
},
}
#[derive(Parser, Debug)]
pub struct ProgressArgs {
#[command(subcommand)]
pub command: ProgressCommand,
}
pub fn handle_progress(
args: &ProgressArgs,
ctx: &InvocationContext,
is_json: bool,
) -> Result<ExitCode, ExitCode> {
if let Some(result) = check_dry_run(ctx, &format!("progress {:?}", args.command)) {
return result;
}
let mut runtime = try_open_runtime(ctx)?;
let project_id = &runtime.config.project.id;
let conn = runtime.database.connection_mut();
let now = chrono::Utc::now().to_rfc3339();
let progress_repo = SqliteProgressRepository::new(conn);
let event_repo = SqliteEventRepository::new(conn);
let task_repo = SqliteTaskRepository::new(conn);
match &args.command {
ProgressCommand::Todo { content, task }
| ProgressCommand::Block { content, task }
| ProgressCommand::Risk { content, task }
| ProgressCommand::Note { content, task } => {
let item_type = match &args.command {
ProgressCommand::Todo { .. } => ProgressType::Todo,
ProgressCommand::Block { .. } => ProgressType::Blocker,
ProgressCommand::Risk { .. } => ProgressType::Risk,
ProgressCommand::Note { .. } => ProgressType::Note,
_ => unreachable!(),
};
let task_id = match task.clone().or_else(|| ctx.task.clone()) {
Some(id) => id,
None => {
return render_and_print::<serde_json::Value>(
"progress.create",
Err(CarryCtxError::validation_error(
"No task specified. Provide --task <TASK_REF>.",
)),
is_json,
ctx.quiet,
);
}
};
let input = application::progress::CreateProgressInput {
project_id: project_id.to_string(),
task_id,
source_session_id: ctx.session.clone(),
item_type,
content: content.clone(),
};
let result = application::progress::create_progress(
&progress_repo,
&task_repo,
&event_repo,
&input,
&now,
);
render_and_print("progress.create", result, is_json, ctx.quiet)
}
ProgressCommand::List { task } => {
let task_id = match task.clone() {
Some(id) => id,
None => {
return render_and_print::<serde_json::Value>(
"progress.list",
Err(CarryCtxError::validation_error(
"No task specified. Provide --task <TASK_REF>.",
)),
is_json,
ctx.quiet,
);
}
};
let filter = ProgressFilter {
project_id: project_id.to_string(),
task_id,
include_removed: false,
};
let result = application::progress::list_progress(&progress_repo, &task_repo, &filter);
if ctx.format == carryctx::application::runtime::OutputFormat::Markdown {
let md = match &result {
Ok(items) => {
let mut out = String::from("# Progress Items\n\n");
out.push_str("| ID | Type | Content | Status | Position |\n");
out.push_str("|---|---|---|---|---|\n");
for p in items {
let content_short = if p.content.len() > 40 {
format!("{}...", &p.content[..40])
} else {
p.content.clone()
};
out.push_str(&format!(
"| {} | {:?} | {} | {:?} | {} |\n",
p.display_id, p.item_type, content_short, p.status, p.position
));
}
out
}
Err(e) => format!("Error: {e}"),
};
if !ctx.quiet {
print!("{md}");
}
return Ok(ExitCode::Success);
}
render_and_print("progress.list", result, is_json, ctx.quiet)
}
ProgressCommand::Show { progress_ref } => {
let item = progress_repo
.find_by_display_id(project_id, progress_ref)
.map_err(|e| e.exit_code)?
.or_else(|| {
progress_repo
.find_by_id(project_id, progress_ref)
.ok()
.flatten()
})
.ok_or(ExitCode::ResourceNotFound)?;
render_and_print("progress.show", Ok(item), is_json, ctx.quiet)
}
ProgressCommand::Edit {
progress_ref,
content,
} => {
let input = application::progress::EditProgressInput {
project_id: project_id.to_string(),
ref_or_id: progress_ref.clone(),
content: content.clone(),
};
let result =
application::progress::edit_progress(&progress_repo, &event_repo, &input, &now);
render_and_print("progress.edit", result, is_json, ctx.quiet)
}
ProgressCommand::Complete { progress_ref } => {
let result = application::progress::complete_progress(
&progress_repo,
&event_repo,
project_id,
progress_ref,
&now,
);
render_and_print("progress.complete", result, is_json, ctx.quiet)
}
ProgressCommand::Reopen { progress_ref } => {
let result = application::progress::reopen_progress(
&progress_repo,
&event_repo,
project_id,
progress_ref,
&now,
);
render_and_print("progress.reopen", result, is_json, ctx.quiet)
}
ProgressCommand::Remove { progress_ref } => {
let result = application::progress::remove_progress(
&progress_repo,
&event_repo,
project_id,
progress_ref,
&now,
);
render_and_print("progress.remove", result, is_json, ctx.quiet)
}
ProgressCommand::Reorder { task, order } => {
let input = application::progress::ReorderProgressInput {
project_id: project_id.to_string(),
task_id: task.clone(),
ordered_refs: order.clone(),
};
let result = application::progress::reorder_progress(
&progress_repo,
&task_repo,
&event_repo,
&input,
&now,
);
render_and_print("progress.reorder", result, is_json, ctx.quiet)
}
}
}