use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand};
use jk_cli::{
DiffFormat, DiffQuery, JjAbandon, JjDescribe, JjDiff, JjEdit, JjEvolog, JjLog, JjLogCommand,
JjNew, JjOperation, JjRecovery, JjShow, JjStatus, JjWorkspaces, LogTemplateSelection,
ShowQuery, StatusQuery,
};
#[derive(Debug, Parser)]
#[command(version, about)]
pub struct Args {
#[arg(short = 'R', long = "repository")]
pub(crate) repository: Option<PathBuf>,
#[arg(short = 'n', long)]
pub(crate) limit: Option<usize>,
#[command(subcommand)]
pub(crate) command: Option<Command>,
}
impl Args {
pub(crate) fn log_source(&self) -> JjLog {
let (command, limit, template) = match &self.command {
Some(Command::Log(log_args)) => (
JjLogCommand::Log,
log_args.limit.or(self.limit),
log_args.template.clone().map_or(
LogTemplateSelection::Configured,
LogTemplateSelection::Custom,
),
),
Some(
Command::Diff(_) | Command::Show(_) | Command::Status(_) | Command::Workspaces,
)
| None => (
JjLogCommand::ConfiguredDefault,
self.limit,
LogTemplateSelection::Configured,
),
};
let source = JjLog::default()
.with_command(command)
.with_limit(limit)
.with_template(template);
self.with_repository(source)
}
pub(crate) fn diff_source(&self) -> JjDiff {
self.with_repository(JjDiff::default())
}
pub(crate) fn show_source(&self) -> JjShow {
self.with_repository(JjShow::default())
}
pub(crate) fn evolog_source(&self) -> JjEvolog {
self.with_repository(JjEvolog::default())
}
pub(crate) fn status_source(&self) -> JjStatus {
self.with_repository(JjStatus::default())
}
pub(crate) fn describe_source(&self) -> JjDescribe {
self.with_repository(JjDescribe::default())
}
pub(crate) fn abandon_source(&self) -> JjAbandon {
self.with_repository(JjAbandon::default())
}
pub(crate) fn new_source(&self) -> JjNew {
self.with_repository(JjNew::default())
}
pub(crate) fn edit_source(&self) -> JjEdit {
self.with_repository(JjEdit::default())
}
pub(crate) fn operation_source(&self) -> JjOperation {
self.with_repository(JjOperation::default())
}
pub(crate) fn recovery_source(&self) -> JjRecovery {
self.with_repository(JjRecovery::default())
}
pub(crate) fn workspaces_source(&self) -> JjWorkspaces {
self.with_repository(JjWorkspaces::default())
}
fn with_repository<T>(&self, source: T) -> T
where
T: WithRepository,
{
if let Some(repository) = self.repository.as_deref() {
source.with_repository(repository)
} else {
source
}
}
}
trait WithRepository: Sized {
fn with_repository(self, repository: &Path) -> Self;
}
macro_rules! impl_with_repository {
($($source:ty),+ $(,)?) => {
$(
impl WithRepository for $source {
fn with_repository(self, repository: &Path) -> Self {
<$source>::with_repository(self, repository)
}
}
)+
};
}
impl_with_repository!(
JjAbandon,
JjDescribe,
JjDiff,
JjEdit,
JjEvolog,
JjLog,
JjNew,
JjOperation,
JjRecovery,
JjShow,
JjStatus,
JjWorkspaces,
);
#[derive(Debug, Subcommand)]
pub enum Command {
Log(LogArgs),
Diff(DiffArgs),
Show(ShowArgs),
Status(StatusArgs),
Workspaces,
}
#[derive(Debug, Parser)]
pub struct LogArgs {
#[arg(short = 'n', long)]
limit: Option<usize>,
#[arg(short = 'T', long = "template", value_name = "TEMPLATE")]
pub(crate) template: Option<String>,
}
#[derive(Debug, Parser)]
pub struct DiffArgs {
#[arg(value_name = "REV", conflicts_with_all = ["revision", "from", "to"])]
compatibility_revision: Option<String>,
#[arg(short = 'r', long = "revision", value_name = "REV", conflicts_with_all = ["from", "to"])]
revision: Option<String>,
#[arg(
long,
value_name = "FROM",
requires = "to",
conflicts_with = "revision"
)]
from: Option<String>,
#[arg(
long,
value_name = "TO",
requires = "from",
conflicts_with = "revision"
)]
to: Option<String>,
#[arg(long, conflicts_with_all = ["summary", "types", "name_only", "git", "color_words"])]
stat: bool,
#[arg(short = 's', long, conflicts_with_all = ["stat", "types", "name_only", "git", "color_words"])]
summary: bool,
#[arg(long, conflicts_with_all = ["stat", "summary", "name_only", "git", "color_words"])]
types: bool,
#[arg(long, conflicts_with_all = ["stat", "summary", "types", "git", "color_words"])]
name_only: bool,
#[arg(long, conflicts_with_all = ["stat", "summary", "types", "name_only", "color_words"])]
git: bool,
#[arg(long, conflicts_with_all = ["stat", "summary", "types", "name_only", "git"])]
color_words: bool,
}
impl DiffArgs {
pub(crate) fn query(&self) -> DiffQuery {
let format = self.format();
if let (Some(from), Some(to)) = (&self.from, &self.to) {
return DiffQuery::FromTo {
from: from.clone(),
to: to.clone(),
format,
};
}
let rev = self
.revision
.as_ref()
.or(self.compatibility_revision.as_ref())
.cloned()
.unwrap_or_else(|| "@".to_owned());
DiffQuery::Revision { rev, format }
}
const fn format(&self) -> DiffFormat {
if self.summary {
DiffFormat::Summary
} else if self.stat {
DiffFormat::Stat
} else if self.types {
DiffFormat::Types
} else if self.name_only {
DiffFormat::NameOnly
} else if self.git {
DiffFormat::Git
} else if self.color_words {
DiffFormat::ColorWords
} else {
DiffFormat::Patch
}
}
}
#[derive(Debug, Parser)]
pub struct ShowArgs {
#[arg(value_name = "REV", required = true)]
revs: Vec<String>,
}
impl ShowArgs {
pub(crate) fn query(&self) -> ShowQuery {
ShowQuery::new(self.revs.clone())
}
}
#[derive(Debug, Parser)]
pub struct StatusArgs {
#[arg(value_name = "FILESET")]
filesets: Vec<String>,
}
impl StatusArgs {
pub(crate) fn query(&self) -> StatusQuery {
StatusQuery::new(self.filesets.clone())
}
}