use std::{path::PathBuf, str::FromStr};
use clap::Parser;
#[cfg(feature = "completions")]
use clap_complete::aot::Shell as Shells;
use semver::{Prerelease, Version};
#[derive(Debug, Parser)]
#[clap(name = "convco", about = "Conventional commit tools", version)]
pub struct Opt {
#[clap(short = 'C', global = true)]
pub path: Option<PathBuf>,
#[clap(short = 'c', long = "config", global = true, env = "CONVCO_CONFIG")]
pub config: Option<PathBuf>,
#[clap(subcommand)]
pub cmd: Command,
}
#[derive(Debug, Parser)]
pub enum Command {
Config(ConfigCommand),
Check(CheckCommand),
#[cfg(feature = "completions")]
Completions(CompletionsCommand),
Changelog(ChangelogCommand),
Version(VersionCommand),
Commit(CommitCommand),
}
#[derive(Debug, Parser)]
pub struct ConfigCommand {
#[clap(short, long)]
pub default: bool,
}
#[derive(Debug, Parser)]
pub struct VersionCommand {
#[clap(short, long, default_value = "v", env = "CONVCO_PREFIX")]
pub prefix: String,
#[clap(long, visible_alias = "pp", env = "CONVCO_PRINT_PREFIX")]
pub print_prefix: bool,
#[clap(default_value = "HEAD")]
pub rev: String,
#[clap(short, long)]
pub bump: bool,
#[clap(short, long, conflicts_with_all(&["major", "minor", "patch"]))]
pub label: bool,
#[clap(long, env = "CONVCO_FORCE_MAJOR_BUMP")]
pub major: bool,
#[clap(long, env = "CONVCO_FORCE_MINOR_BUMP")]
pub minor: bool,
#[clap(long, env = "CONVCO_FORCE_PATCH_BUMP")]
pub patch: bool,
#[clap(long, requires = "bump", default_value_t = Prerelease::new("").unwrap())]
pub prerelease: Prerelease,
#[clap(short = 'P', long, env = "CONVCO_PATHS")]
pub paths: Vec<PathBuf>,
#[clap(long)]
pub commit_sha: bool,
#[clap(long, env = "CONVCO_IGNORE_PRERELEASES")]
pub ignore_prereleases: bool,
#[clap(long, env = "CONVCO_INITIAL_BUMP_VERSION")]
pub initial_bump_version: Option<Version>,
}
#[derive(Debug, Parser)]
pub struct CheckCommand {
#[clap(env = "CONVCO_REV")]
pub rev: Option<String>,
#[clap(short, long = "max-count", env = "CONVCO_MAX_COUNT")]
pub number: Option<usize>,
#[clap(long, env = "CONVCO_MERGES")]
pub merges: bool,
#[clap(long, env = "CONVCO_FIRST_PARENT")]
pub first_parent: bool,
#[clap(long, env = "CONVCO_IGNORE_REVERTS")]
pub ignore_reverts: bool,
#[clap(long)]
pub from_stdin: bool,
#[clap(long, requires("from_stdin"))]
pub strip: bool,
}
#[cfg(feature = "completions")]
#[derive(Debug, Parser)]
pub struct CompletionsCommand {
pub shell: Option<Shells>,
}
#[derive(Debug, Parser)]
pub struct ChangelogCommand {
#[clap(short, long, default_value = "v", env = "CONVCO_PREFIX")]
pub prefix: String,
#[clap(default_value = "HEAD", env = "CONVCO_REV")]
pub rev: String,
#[clap(short, long, env = "CONVCO_SKIP_EMPTY")]
pub skip_empty: bool,
#[clap(short, long, env = "CONVCO_MAX_VERSIONS")]
pub max_versions: Option<usize>,
#[clap(long, default_value_t=u64::MAX, hide_default_value=true, env = "CONVCO_MAX_MINORS")]
pub max_minors: u64,
#[clap(long, default_value_t=u64::MAX, hide_default_value=true, env = "CONVCO_MAX_MAJORS")]
pub max_majors: u64,
#[clap(long, default_value_t=u64::MAX, hide_default_value=true, env = "CONVCO_MAX_PATCHES")]
pub max_patches: u64,
#[clap(long, env = "CONVCO_IGNORE_PRERELEASES")]
pub ignore_prereleases: bool,
#[clap(short, long, env = "CONVCO_NO_LINKS")]
pub no_links: bool,
#[clap(long, env = "CONVCO_MERGES")]
pub merges: bool,
#[clap(long, env = "CONVCO_INCLUDE_HIDDEN_SECTIONS")]
pub include_hidden_sections: bool,
#[clap(short = 'P', long, env = "CONVCO_PATHS")]
pub paths: Vec<PathBuf>,
#[clap(long, env = "CONVCO_FIRST_PARENT")]
pub first_parent: bool,
#[clap(long, env = "CONVCO_LINE_LENGTH")]
pub line_length: Option<usize>,
#[clap(long, env = "CONVCO_NO_WRAP")]
pub no_wrap: bool,
#[clap(short, long, default_value = "Unreleased", env = "CONVCO_UNRELEASED")]
pub unreleased: String,
#[clap(short, long, default_value = "-", env = "CONVCO_OUTPUT")]
pub output: PathBuf,
}
#[derive(Debug, Parser)]
pub struct CommitCommand {
#[clap(long,
conflicts_with_all(&["feat", "build", "chore", "ci", "docs", "style", "refactor", "perf", "test", "type"]),
)]
pub fix: bool,
#[clap(long,
conflicts_with_all(&["fix", "build", "chore", "ci", "docs", "style", "refactor", "perf", "test", "type"]),
)]
pub feat: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "chore", "ci", "docs", "style", "refactor", "perf", "test", "type"]),
)]
pub build: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "build", "ci", "docs", "style", "refactor", "perf", "test", "type"]),
)]
pub chore: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "build", "chore", "docs", "style", "refactor", "perf", "test", "type"]),
)]
pub ci: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "build", "chore", "ci", "style", "refactor", "perf", "test", "type"]),
)]
pub docs: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "build", "chore", "ci", "docs", "refactor", "perf", "test", "type"]),
)]
pub style: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "build", "chore", "ci", "docs", "style", "perf", "test", "type"]),
)]
pub refactor: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "build", "chore", "ci", "docs", "style", "refactor", "test", "type"]),
)]
pub perf: bool,
#[clap(long,
conflicts_with_all(&["feat", "fix", "build", "chore", "ci", "docs", "style", "refactor", "perf", "type"]),
)]
pub test: bool,
#[clap(short, long,
conflicts_with_all(&["feat", "fix", "build", "chore", "ci", "docs", "style", "refactor", "perf", "test"]),
)]
pub r#type: Option<String>,
#[clap(short, long)]
pub scope: Option<String>,
#[clap(short, long)]
pub message: Vec<String>,
#[clap(
short,
long,
visible_alias = "trailer",
value_name = "token>(=|:)<value"
)]
pub footers: Vec<Footer>,
#[clap(long)]
pub breaking: bool,
#[clap(long, short, env = "CONVCO_INTERACTIVE")]
pub interactive: bool,
#[clap(short = 'N', long, env = "CONVCO_INTENT_TO_ADD")]
pub intent_to_add: Vec<PathBuf>,
#[clap(short, long, env = "CONVCO_PATCH")]
pub patch: bool,
#[clap(hide = true)]
pub commit_msg_path: Option<PathBuf>,
#[clap(last = true)]
pub extra_args: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Footer(pub(crate) String, pub(crate) String);
impl From<(&str, &str)> for Footer {
fn from(s: (&str, &str)) -> Self {
Self(s.0.trim().to_owned(), s.1.trim().to_owned())
}
}
impl FromStr for Footer {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split(':');
match (split.next(), split.next()) {
(Some(k), Some(v)) => Ok((k, v).into()),
_ => {
let mut split = s.split('=');
match (split.next(), split.next()) {
(Some(k), Some(v)) => Ok((k, v).into()),
_ => Err(format!("invalid footer: {}", s)),
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_footer() {
let footer: Footer = "Reviewed-by: Z".parse().unwrap();
assert_eq!(Footer("Reviewed-by".into(), "Z".into()), footer);
}
#[test]
fn test_footer2() {
let footer: Footer = "Reviewed-by=Z".parse().unwrap();
assert_eq!(Footer("Reviewed-by".into(), "Z".into()), footer);
}
#[test]
fn test_footer_err_empty() {
let err: String = "".parse::<Footer>().unwrap_err();
assert_eq!(err, format!("invalid footer: {}", ""));
}
}