use std::path::PathBuf;
use prikk_store::{DEFAULT_CHECKOUT_REF, DEFAULT_HISTORY_LIMIT};
mod checkout;
mod merge_evidence;
pub(crate) use checkout::{CheckoutMode, parse_checkout_args};
pub(crate) use merge_evidence::{MergeEvidenceTargetArg, parse_merge_evidence_args};
pub(crate) struct CommitArgs {
pub(crate) message: String,
pub(crate) ref_name: String,
pub(crate) text_edits: bool,
}
pub(crate) struct LogArgs {
pub(crate) root: PathBuf,
pub(crate) ref_name: String,
pub(crate) limit: usize,
}
pub(crate) struct InversePlanArgs {
pub(crate) root: PathBuf,
pub(crate) ref_name: String,
}
pub(crate) struct RollbackPreviewArgs {
pub(crate) root: PathBuf,
pub(crate) ref_name: String,
}
pub(crate) struct RollbackDraftArgs {
pub(crate) root: PathBuf,
pub(crate) ref_name: String,
pub(crate) message: String,
}
pub(crate) struct RollbackDraftVerifyArgs {
pub(crate) root: PathBuf,
pub(crate) ref_name: String,
}
pub(crate) struct WorktreeStatusArgs {
pub(crate) root: PathBuf,
pub(crate) ref_name: String,
}
pub(crate) struct DoctorArgs {
pub(crate) root: PathBuf,
pub(crate) repair_wal_tail: bool,
pub(crate) repair_main_ref: bool,
}
pub(crate) fn parse_log_args(args: Vec<String>) -> std::result::Result<LogArgs, String> {
let mut path = None;
let mut ref_name = "heads/main".to_string();
let mut limit = DEFAULT_HISTORY_LIMIT;
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--ref" => {
let Some(value) = iter.next() else {
return Err("log --ref requires a value".to_string());
};
if value.trim().is_empty() {
return Err("log --ref must not be empty".to_string());
}
ref_name = value;
}
"--limit" => {
let Some(value) = iter.next() else {
return Err("log --limit requires a value".to_string());
};
limit = value
.parse::<usize>()
.map_err(|_| "log --limit must be a non-negative integer".to_string())?;
}
other if other.starts_with('-') => {
return Err(format!("unknown log argument: {other}"));
}
_ => {
if path.is_some() {
return Err("log accepts at most one path".to_string());
}
path = Some(arg);
}
}
}
Ok(LogArgs {
root: optional_path_or_current(path)?,
ref_name,
limit,
})
}
pub(crate) fn parse_inverse_plan_args(
args: Vec<String>,
) -> std::result::Result<InversePlanArgs, String> {
let mut path = None;
let mut ref_name = DEFAULT_CHECKOUT_REF.to_string();
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--ref" => {
let Some(value) = iter.next() else {
return Err("inverse-plan --ref requires a value".to_string());
};
if value.trim().is_empty() {
return Err("inverse-plan --ref must not be empty".to_string());
}
ref_name = value;
}
other if other.starts_with('-') => {
return Err(format!("unknown inverse-plan argument: {other}"));
}
_ => {
if path.is_some() {
return Err("inverse-plan accepts at most one path".to_string());
}
path = Some(arg);
}
}
}
Ok(InversePlanArgs {
root: optional_path_or_current(path)?,
ref_name,
})
}
pub(crate) fn parse_rollback_preview_args(
args: Vec<String>,
) -> std::result::Result<RollbackPreviewArgs, String> {
let mut path = None;
let mut ref_name = DEFAULT_CHECKOUT_REF.to_string();
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--ref" => {
let Some(value) = iter.next() else {
return Err("rollback-preview --ref requires a value".to_string());
};
if value.trim().is_empty() {
return Err("rollback-preview --ref must not be empty".to_string());
}
ref_name = value;
}
other if other.starts_with('-') => {
return Err(format!("unknown rollback-preview argument: {other}"));
}
_ => {
if path.is_some() {
return Err("rollback-preview accepts at most one path".to_string());
}
path = Some(arg);
}
}
}
Ok(RollbackPreviewArgs {
root: optional_path_or_current(path)?,
ref_name,
})
}
pub(crate) fn parse_rollback_draft_args(
args: Vec<String>,
) -> std::result::Result<RollbackDraftArgs, String> {
let mut path = None;
let mut ref_name = DEFAULT_CHECKOUT_REF.to_string();
let mut message = None;
let mut append_inverse = false;
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--append-inverse" => append_inverse = true,
"--ref" => {
let Some(value) = iter.next() else {
return Err("rollback-draft --ref requires a value".to_string());
};
if value.trim().is_empty() {
return Err("rollback-draft --ref must not be empty".to_string());
}
ref_name = value;
}
"-m" | "--message" => {
let Some(value) = iter.next() else {
return Err("rollback-draft message option requires a value".to_string());
};
message = Some(value);
}
other if other.starts_with('-') => {
return Err(format!("unknown rollback-draft argument: {other}"));
}
_ => {
if path.is_some() {
return Err("rollback-draft accepts at most one path".to_string());
}
path = Some(arg);
}
}
}
if !append_inverse {
return Err("rollback-draft requires --append-inverse".to_string());
}
let Some(message) = message else {
return Err("rollback-draft requires -m <message>".to_string());
};
if message.trim().is_empty() {
return Err("rollback-draft message must not be empty".to_string());
}
Ok(RollbackDraftArgs {
root: optional_path_or_current(path)?,
ref_name,
message,
})
}
pub(crate) fn parse_rollback_draft_verify_args(
args: Vec<String>,
) -> std::result::Result<RollbackDraftVerifyArgs, String> {
let mut path = None;
let mut ref_name = DEFAULT_CHECKOUT_REF.to_string();
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--ref" => {
let Some(value) = iter.next() else {
return Err("rollback-draft-verify --ref requires a value".to_string());
};
if value.trim().is_empty() {
return Err("rollback-draft-verify --ref must not be empty".to_string());
}
ref_name = value;
}
other if other.starts_with('-') => {
return Err(format!("unknown rollback-draft-verify argument: {other}"));
}
_ => {
if path.is_some() {
return Err("rollback-draft-verify accepts at most one path".to_string());
}
path = Some(arg);
}
}
}
Ok(RollbackDraftVerifyArgs {
root: optional_path_or_current(path)?,
ref_name,
})
}
pub(crate) fn parse_worktree_status_args(
args: Vec<String>,
) -> std::result::Result<WorktreeStatusArgs, String> {
let mut path = None;
let mut ref_name = DEFAULT_CHECKOUT_REF.to_string();
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--ref" => {
let Some(value) = iter.next() else {
return Err("worktree-status --ref requires a value".to_string());
};
if value.trim().is_empty() {
return Err("worktree-status --ref must not be empty".to_string());
}
ref_name = value;
}
other if other.starts_with('-') => {
return Err(format!("unknown worktree-status argument: {other}"));
}
_ => {
if path.is_some() {
return Err("worktree-status accepts at most one path".to_string());
}
path = Some(arg);
}
}
}
Ok(WorktreeStatusArgs {
root: optional_path_or_current(path)?,
ref_name,
})
}
pub(crate) fn parse_doctor_args(args: Vec<String>) -> std::result::Result<DoctorArgs, String> {
let mut repair_wal_tail = false;
let mut repair_main_ref = false;
let mut path = None;
for arg in args {
match arg.as_str() {
"--repair-wal-tail" => repair_wal_tail = true,
"--repair-main-ref" => repair_main_ref = true,
other if other.starts_with('-') => {
return Err(format!("unknown doctor argument: {other}"));
}
_ => {
if path.is_some() {
return Err("doctor accepts at most one path".to_string());
}
path = Some(arg);
}
}
}
Ok(DoctorArgs {
root: optional_path_or_current(path)?,
repair_wal_tail,
repair_main_ref,
})
}
pub(crate) fn parse_commit_args(args: Vec<String>) -> std::result::Result<CommitArgs, String> {
let mut message = None;
let mut ref_name = DEFAULT_CHECKOUT_REF.to_string();
let mut text_edits = false;
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--from-worktree" => {}
"--text-edits" => text_edits = true,
"--ref" => {
let Some(value) = iter.next() else {
return Err("commit --ref requires a value".to_string());
};
if value.trim().is_empty() {
return Err("commit --ref must not be empty".to_string());
}
ref_name = value;
}
"-m" | "--message" => {
let Some(value) = iter.next() else {
return Err("commit message option requires a value".to_string());
};
message = Some(value);
}
other => return Err(format!("unknown commit argument: {other}")),
}
}
let Some(message) = message else {
return Err(
"commit requires -m <message> (usage: prikk commit [--from-worktree] [--text-edits] \
[--ref <name>] -m <message>)"
.to_string(),
);
};
if message.trim().is_empty() {
return Err("commit message must not be empty".to_string());
}
Ok(CommitArgs {
message,
ref_name,
text_edits,
})
}
pub(crate) fn optional_path_or_current(
path: Option<String>,
) -> std::result::Result<PathBuf, String> {
match path {
Some(path) => Ok(PathBuf::from(path)),
None => current_dir(),
}
}
pub(crate) fn current_dir() -> std::result::Result<PathBuf, String> {
std::env::current_dir().map_err(|err| err.to_string())
}