use cclog::{Clog, LinkStyle as ClogLinkStyle, fmt::ChangelogFormat};
use clap::{Parser, ValueEnum};
use strum::{Display, EnumString};
use crate::{
DEFAULT_CONFIG_FILE,
error::{CliError, CliResult},
};
static VERSION: &str = env!("CARGO_PKG_VERSION");
static AFTER_HELP: &str = "
If your .git directory is a child of your project directory (most common, such \
as /myproject/.git) AND not in the current working directory (i.e you need to \
use --work-tree or --git-dir) you only need to specify either the --work-tree \
(i.e. /myproject) OR --git-dir (i.e. /myproject/.git), you don't need to use \
both.
If using the --config to specify a clog configuration TOML file NOT in the \
current working directory (meaning you need to use --work-tree or --git-dir) \
AND the TOML file is inside your project directory (i.e. \
/myproject/.clog.toml) you do not need to use --work-tree or --git-dir.
";
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, ValueEnum, EnumString, Display)]
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
pub enum OutFormat {
#[default]
Markdown,
Json,
}
impl From<OutFormat> for ChangelogFormat {
fn from(of: OutFormat) -> ChangelogFormat {
match of {
OutFormat::Markdown => ChangelogFormat::Markdown,
OutFormat::Json => ChangelogFormat::Json,
}
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, ValueEnum, EnumString, Display)]
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
pub enum LinkStyle {
#[default]
Github,
Gitlab,
Stash,
Cgit,
}
impl From<LinkStyle> for ClogLinkStyle {
fn from(ls: LinkStyle) -> ClogLinkStyle {
match ls {
LinkStyle::Github => ClogLinkStyle::Github,
LinkStyle::Gitlab => ClogLinkStyle::Gitlab,
LinkStyle::Stash => ClogLinkStyle::Stash,
LinkStyle::Cgit => ClogLinkStyle::Cgit,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
#[command(name= "clog", version = VERSION, after_help = AFTER_HELP)]
pub struct Args {
#[arg(short, long, value_name = "URL")]
pub repository: Option<String>,
#[arg(short, long, value_name = "COMMIT")]
pub from: Option<String>,
#[arg(short = 'T', long, value_name = "STR", default_value_t)]
pub format: OutFormat,
#[arg(short = 'M', long)]
pub major: bool,
#[arg(short, long, value_name = "PATH")]
pub git_dir: Option<String>,
#[arg(short, long, value_name = "PATH")]
pub work_tree: Option<String>,
#[arg(short, long)]
pub minor: bool,
#[arg(short, long)]
pub patch: bool,
#[arg(short, long, value_name = "STR")]
pub subtitle: Option<String>,
#[arg(short, long, value_name = "COMMIT", default_value = "HEAD")]
pub to: String,
#[arg(short, long, value_name = "PATH")]
pub outfile: Option<String>,
#[arg(short, long, value_name = "COMMIT", default_value = DEFAULT_CONFIG_FILE)]
pub config: String,
#[arg(short, long, value_name = "PATH")]
pub infile: Option<String>,
#[arg(long, value_name = "VER", group = "setver")]
pub setversion: Option<String>,
#[arg(short = 'F', long, conflicts_with = "from")]
pub from_latest_tag: bool,
#[arg(short, long, value_name = "STR", default_value_t)]
pub link_style: LinkStyle,
#[arg(short = 'C', long, value_name = "PATH", conflicts_with_all = ["infile", "outfile"])]
pub changelog: Option<String>,
}
impl Args {
pub fn into_clog(self) -> CliResult<Clog> {
debugln!("Creating clog from matches");
let mut clog = if let (Some(git_dir), Some(work_tree)) = (&self.git_dir, &self.work_tree) {
debugln!(
"User passed in both\n\tworking dir: {:?}\n\tgit dir: {:?}",
work_tree,
git_dir
);
Clog::new()?.git_dir(git_dir).git_work_tree(work_tree)
} else if let Some(dir) = &self.work_tree {
debugln!("User passed in working dir: {:?}", dir);
Clog::from_config(&self.config)?.git_work_tree(dir)
} else if let Some(dir) = &self.git_dir {
debugln!("User passed in git dir: {:?}", dir);
Clog::from_config(&self.config)?.git_dir(dir)
} else {
debugln!("User only passed config");
Clog::from_config(&self.config)?
};
clog.version = {
let (major, minor, patch) = (self.major, self.minor, self.patch);
if self.setversion.is_some() {
self.setversion
} else if major || minor || patch {
let mut had_v = false;
let v_string = clog.get_latest_tag_ver()?;
let first_char = v_string.chars().next().unwrap_or(' ');
let v_slice = if first_char == 'v' || first_char == 'V' {
had_v = true;
v_string.trim_start_matches(['v', 'V'])
} else {
&v_string[..]
};
match semver::Version::parse(v_slice) {
Ok(ref mut v) => {
match (major, minor, patch) {
(true, _, _) => {
v.major += 1;
v.minor = 0;
v.patch = 0;
}
(_, true, _) => {
v.minor += 1;
v.patch = 0;
}
(_, _, true) => {
v.patch += 1;
clog.patch_ver = true;
}
_ => unreachable!(),
}
Some(format!("{}{v}", if had_v { "v" } else { "" }))
}
Err(e) => {
return Err(CliError::Semver(
Box::new(e),
String::from(
"Failed to parse version into valid SemVer. \
Ensure the version is in the X.Y.Z format.",
),
));
}
}
} else {
clog.version
}
};
if let Some(from) = &self.from {
clog.from = Some(from.to_owned());
} else if self.from_latest_tag {
clog.from = Some(clog.get_latest_tag()?);
}
if let Some(file) = &self.outfile {
clog.outfile = Some(file.to_owned());
}
if let Some(file) = &self.infile {
clog.infile = Some(file.to_owned());
}
if let Some(file) = &self.changelog {
clog.infile = Some(file.to_owned());
clog.outfile = Some(file.to_owned());
}
clog.repo = self.repository;
clog.subtitle = self.subtitle;
clog.link_style = self.link_style.into();
clog.out_format = self.format.into();
self.to.clone_into(&mut clog.to);
debugln!("Returning clog:\n{:?}", clog);
Ok(clog)
}
}