use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::{collections::HashSet, fmt::Display};
use changelog::fmt::SortOptions;
use clap::{arg, Args, Parser, Subcommand, ValueHint};
use changelog::ser::{Options, OptionsRelease};
use changelog::Version;
use clap::ValueEnum;
use indexmap::IndexMap;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::git_provider::GitProvider;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapMessageToSection(pub IndexMap<String, HashSet<String>>);
impl Default for MapMessageToSection {
fn default() -> Self {
let map = include_str!("../res/map_commit_type_to_section.json");
serde_json::de::from_str(map).unwrap()
}
}
impl MapMessageToSection {
pub fn to_fmt_options(self) -> changelog::fmt::Options {
changelog::fmt::Options {
sort_options: SortOptions {
section_order: self.0.into_iter().map(|(section, _)| section).collect(),
..Default::default()
},
}
}
pub fn into_changelog_ser_options(self) -> Options {
Options {
release_option: OptionsRelease {
..Default::default()
},
}
}
pub fn map_section(&self, section: &str) -> Option<String> {
let section_normalized = section.to_lowercase();
for (section, needles) in &self.0 {
for needle in needles {
let needle_normalized = needle.to_lowercase();
if section_normalized == needle_normalized {
return Some(section.to_owned());
}
}
}
None
}
pub fn try_find_section(&self, (message, desc): (&str, &str)) -> Option<String> {
let message_normalized = message.to_lowercase();
let desc_normalized = desc.to_lowercase();
for (section, needles) in &self.0 {
for needle in needles {
let needle_normalized = needle.to_lowercase();
if message_normalized.contains(&needle_normalized) {
return Some(section.to_owned());
}
if desc_normalized.contains(&needle_normalized) {
return Some(section.to_owned());
}
}
}
None
}
pub fn try_new<P: AsRef<Path>>(path: Option<P>) -> anyhow::Result<MapMessageToSection> {
match path {
Some(path) => {
let mut file = File::open(&path)?;
let mut content = Vec::new();
file.read_to_end(&mut content)?;
let map = serde_json::de::from_slice(&content)?;
Ok(map)
}
None => Ok(MapMessageToSection::default()),
}
}
}
#[derive(ValueEnum, Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub enum CommitMessageParsing {
#[default]
Smart,
Strict,
}
impl Display for CommitMessageParsing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CommitMessageParsing::Smart => write!(f, "smart"),
CommitMessageParsing::Strict => write!(f, "strict"),
}
}
}
#[derive(ValueEnum, Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub enum MergeDevVersions {
#[default]
Auto,
No,
Yes,
}
impl Display for MergeDevVersions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MergeDevVersions::Auto => write!(f, "auto"),
MergeDevVersions::No => write!(f, "no"),
MergeDevVersions::Yes => write!(f, "yes"),
}
}
}
#[derive(Debug, Clone, Parser)]
#[command(version, about = "Changelog generator")]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Debug, Clone, Subcommand)]
pub enum Commands {
New(New),
Validate(Validate),
#[command(alias = "gen")]
Generate(Generate),
Release(Release),
Show(Show),
#[command(aliases = ["delete", "rm"])]
Remove(Remove),
}
#[derive(Debug, Clone, Args)]
pub struct Generate {
#[arg(
short,
long,
default_value = "CHANGELOG.md",
value_hint = ValueHint::FilePath,
short_alias = 'o',
alias = "output",
)]
pub file: Option<PathBuf>,
#[arg(long, value_hint = ValueHint::FilePath)]
pub map: Option<PathBuf>,
#[arg(long, default_value_t)]
pub parsing: CommitMessageParsing,
#[arg(long)]
pub exclude_unidentified: bool,
#[arg(long)]
pub exclude_not_pr: bool,
#[arg(long, default_value_t)]
pub provider: GitProvider,
#[arg(long)]
pub repo: Option<String>,
#[arg(long)]
pub omit_pr_link: bool,
#[arg(long)]
pub omit_thanks: bool,
#[arg(long)]
pub stdout: bool,
#[arg(
long,
conflicts_with_all = ["milestone", "since", "until"],
)]
pub specific: Option<String>,
#[arg(
long,
conflicts_with_all = ["since", "until"],
)]
pub milestone: Option<String>,
#[arg(long)]
pub since: Option<String>,
#[arg(long, requires = "since")]
pub until: Option<String>,
}
#[derive(Debug, Clone, Args)]
pub struct Release {
#[arg(
short,
long,
default_value = "CHANGELOG.md",
value_hint = ValueHint::FilePath,
)]
pub file: Option<PathBuf>,
#[arg(
short,
long,
num_args(0..=1),
default_missing_value=None
)]
pub version: Option<Version>,
#[arg(long)]
pub previous_version: Option<Version>,
#[arg(long, default_value_t)]
pub provider: GitProvider,
#[arg(long)]
pub repo: Option<String>,
#[arg(long)]
pub omit_diff: bool,
#[arg(long)]
pub force: bool,
#[arg(long)]
pub header: Option<String>,
#[arg(long, default_value_t)]
pub merge_dev_versions: MergeDevVersions,
#[arg(long)]
pub stdout: bool,
}
#[derive(Debug, Clone, Args)]
pub struct Validate {
#[arg(
short,
long,
default_value = "CHANGELOG.md",
value_hint = ValueHint::FilePath,
)]
pub file: Option<PathBuf>,
#[arg(long, alias = "fmt")]
pub format: bool,
#[arg(long, value_hint = ValueHint::FilePath)]
pub map: Option<PathBuf>,
#[arg(long)]
pub ast: bool,
#[arg(long)]
pub stdout: bool,
}
#[derive(Debug, Clone, Args)]
pub struct Show {
#[arg(
short,
long,
default_value = "CHANGELOG.md",
value_hint = ValueHint::FilePath,
)]
pub file: Option<PathBuf>,
#[arg(
short,
default_value_t = 0,
conflicts_with = "version",
allow_hyphen_values = true
)]
pub n: i32,
#[arg(
short,
long,
num_args(0..=1),
default_missing_value=None
)]
pub version: Option<Regex>,
}
#[derive(Debug, Clone, Args)]
pub struct New {
#[arg(
short,
long,
default_value = "CHANGELOG.md",
value_hint = ValueHint::FilePath,
)]
pub path: Option<PathBuf>,
#[arg(short, long)]
pub force: bool,
}
#[derive(Debug, Clone, Args)]
pub struct Remove {
#[arg(
short,
long,
default_value = "CHANGELOG.md",
value_hint = ValueHint::FilePath,
)]
pub file: Option<PathBuf>,
#[arg(long)]
pub stdout: bool,
#[clap(flatten)]
pub remove_id: RemoveSelection,
}
#[derive(Debug, Clone, Args)]
#[group(required = true, multiple = false)]
pub struct RemoveSelection {
#[arg(short, conflicts_with = "version", allow_hyphen_values = true)]
pub n: Option<i32>,
#[arg(short, long)]
pub version: Option<Regex>,
}