XAB427Y43CDIGVHXNEOZC4J4KX5PRFJIX6NJADQR6LP6OWHPNC2AC HFXK2C7HC4PTDDDAYOKGYLPESYOXWVLLZ622H3KYUSI24DIELU7QC WLLL2ADLZCFVCKAFSF3HWIICTDPAK3NTEYK3ZAGSP5YEPSKNU4KQC CUZW3MMNXRGZEPWDI6M6QIAZPEQHA35KMXO7VWTPYBLXVLT7CMOAC CHJDF2CR5BZF5FQN6ZQQKU6DS4UXTDYMPPCCCBOWR6DGWNW3QR4QC 5P5MR7NKRHWVAJFHTYHLYAPSWHM5CWOVERMRRAWABP4J7JM5YKHAC DMZZMIA7AL72S6YU5LB2PS5V5WNALGKPCGX2SIGXWTKFHRXL7ALQC 2TXT5LGDNIJNMM4EORWKP35BGREM3RPDQ7IUEXZFKQ4N6LGNPLWQC VUSECHH6JNFVRXXS5ZP7NBGO3BQWBJZCWRJCYF2SKOGZLKRYLK4QC 4LFON6GEZVZPITBA7AZCNARXHUSRHWGFYQMR2X6JEVBGHKHECVTAC 4FCEKFETVRRY5ZNAO44GFUCEIXYCM3J2MRTUQ7CUYZ2ZHANWI2MAC 27L7IZBULKDP76DFPUBKKJHLP7XIXN3KFKSMYS35TU6Y574LUUYQC HXCFU6ZFK2G33HC3VX2PAQTL3UJK4BG472O4G6T5YF3ZOQKIE4HQC M3X5J5GHHZXBPTFXDCHWQKQPWUVRUH6RJNGVWIA44B57J4T3O75AC KPQC63IAT2RE3DTDMWE7Z6N5DL4RRWB7QCONH4EB36RTQ7V3DULQC name = "console"version = "0.11.3"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)","lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)","libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)","regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)","terminal_size 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)","termios 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)","unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)","winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)","winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]
name = "dialoguer"version = "0.6.2"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["console 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)","lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)","tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]
][[package]]name = "getrandom"version = "0.1.14"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)","libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)","wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
name = "rand"version = "0.7.3"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)","libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)","rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)","rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)","rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]name = "rand_chacha"version = "0.2.2"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["ppv-lite86 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)","rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]name = "rand_core"version = "0.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]name = "rand_hc"version = "0.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]name = "redox_syscall"version = "0.1.57"source = "registry+https://github.com/rust-lang/crates.io-index"[[package]]
][[package]]name = "tempfile"version = "3.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)","libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)","rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)","redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)","remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)","winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
name = "terminal_size"version = "0.1.13"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)","winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]name = "termios"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"dependencies = ["libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)",][[package]]
"checksum console 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a""checksum dialoguer 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4aa86af7b19b40ef9cbef761ed411a49f0afa06b7b6dcd3dfe2f96a3c546138"
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03""checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402""checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19""checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c""checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9""checksum terminal_size 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13""checksum termios 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
fn read_single_line(prompt: &str) -> Result<String, Error> {let mut out = io::stdout();write!(out, "{}", prompt)?;out.flush()?;let mut value = String::new();io::stdin().read_line(&mut value)?;// remove the newlineOk(value.trim().to_owned())
fn read_single_line(theme: &impl dialoguer::theme::Theme,prompt: &str,default: &str,) -> Result<String, Error> {Ok(dialoguer::Input::with_theme(theme).with_prompt(prompt).default(default.to_string()).allow_empty(true).interact()?)
fn read_multi_line(prompt: &str) -> Result<String, Error> {let mut out = io::stdout();writeln!(out, "{}", prompt)?;writeln!(out, "Press CTRL+D to stop")?;out.flush()?;let mut value = String::new();io::stdin().read_to_string(&mut value)?;// remove the newlinesOk(value.trim().to_owned())
fn make_commit_message(Dialog {r#type,scope,description,body,breaking_change,issues,}: &Dialog,breaking: bool,parser: &CommitParser,) -> Result<String, Error> {let mut msg = r#type.to_string();if !scope.is_empty() {msg.push('(');msg.push_str(scope.as_str());msg.push(')');}if breaking || !breaking_change.is_empty() {msg.push('!');}msg.push_str(": ");msg.push_str(description.as_str());if !body.is_empty() {msg.push_str("\n\n");msg.push_str(body.as_str())}if !breaking_change.is_empty() {msg.push_str("\n\n");msg.push_str(format!("BREAKING CHANGE: {}", breaking_change).as_str());}if !issues.is_empty() {msg.push_str("\n\n");msg.push_str(format!("Refs: {}", issues).as_str());}// validate by parsingOk(parser.parse(msg.as_str()).map(|_| msg)?)
fn type_as_string(&self) -> &str {if self.build {"build"} else if self.chore {"chore"} else if self.ci {"ci"} else if self.docs {"docs"} else if self.feat {"feat"} else if self.fix {"fix"} else if self.perf {"perf"} else if self.refactor {"refactor"} else if self.style {"style"} else if self.test {"test"} else {unreachable!()}}fn commit(&self,scope: String,description: String,body: String,breaking_change: String,issues: String,parser: CommitParser,) -> Result<ExitStatus, Error> {let mut msg = self.type_as_string().to_owned();if !scope.is_empty() {msg.push('(');msg.push_str(scope.as_str());msg.push(')');}if self.breaking || !breaking_change.is_empty() {msg.push('!');}msg.push_str(": ");msg.push_str(description.as_str());if !body.is_empty() {msg.push_str("\n\n");msg.push_str(body.as_str())}if !breaking_change.is_empty() {msg.push_str("\n\n");msg.push_str(format!("BREAKING CHANGE: {}", breaking_change).as_str());}if !issues.is_empty() {msg.push_str("\n\n");msg.push_str(format!("Refs: {}", issues).as_str());}// validate by parsingparser.parse(msg.as_str()).expect("Matches conventional commit");
fn commit(&self, msg: String) -> Result<ExitStatus, Error> {
fn read_scope(theme: &impl dialoguer::theme::Theme,default: &str,scope_regex: Regex,) -> Result<String, Error> {let result: String = dialoguer::Input::with_theme(theme).with_prompt("scope").validate_with(move |input: &str| match scope_regex.is_match(input) {true => Ok(()),false => {if input.is_empty() {Ok(())} else {Err(format!("scope does not match regex {:?}", scope_regex))}}}).default(default.to_string()).allow_empty(true).interact()?;Ok(result)}fn read_description(theme: &impl dialoguer::theme::Theme,default: String,) -> Result<String, Error> {let result: String = dialoguer::Input::with_theme(theme).with_prompt("description").validate_with(|input: &str| {if input.len() < 10 {Err("Description needs a length of at least 10 characters")} else {Ok(())}}).default(default).allow_empty(false).interact()?;Ok(result)}fn read_body(default: &str) -> Result<String, Error> {let prompt = if default.is_empty() {"# Enter a commit message body"} else {default};Ok(dialoguer::Editor::new().require_save(true).edit(prompt)?.unwrap_or_default().lines().filter(|line| !line.starts_with('#')).collect::<Vec<&str>>().join("\n").trim().to_owned())}#[derive(Default)]struct Dialog {r#type: String,scope: String,description: String,body: String,breaking_change: String,issues: String,}impl Dialog {fn select_type(theme: &impl dialoguer::theme::Theme,selected: &str,types: &[Type],) -> Result<String, Error> {let index = dialoguer::Select::with_theme(theme).with_prompt("type").items(types).default(types.iter().position(|t| t.r#type == selected).unwrap_or(0)).interact()?;Ok(r#types[index].r#type.clone())}// Prompt allfn wizard(config: &Config,parser: CommitParser,r#type: Option<String>,breaking: bool,) -> Result<String, Error> {let mut dialog = Self::default();let theme = &dialoguer::theme::ColorfulTheme::default();let types = config.types.as_slice();let scope_regex = Regex::new(config.scope_regex.as_str()).expect("valid scope regex");loop {// typelet current_type = dialog.r#type.as_str();match (r#type.as_ref(), current_type) {(Some(t), "") if t != "" => dialog.r#type = t.to_owned(),(_, t) => {dialog.r#type = Self::select_type(theme, t, types)?;}}// scopedialog.scope = read_scope(theme, dialog.scope.as_ref(), scope_regex.clone())?;// descriptiondialog.description = read_description(theme, dialog.description)?;// bodydialog.body = read_body(dialog.body.as_str())?;// breaking changedialog.breaking_change = read_single_line(theme,"optional BREAKING change",dialog.breaking_change.as_str(),)?;// issuesdialog.issues =read_single_line(theme, "issues (e.g. #2, #8)", dialog.issues.as_str())?;// finally make messagematch make_commit_message(&dialog, breaking, &parser) {Ok(msg) => {if dialoguer::Confirm::with_theme(theme).with_prompt(format!("\nConfirm commit message:\n\n{}\n", msg)).interact()?{break Ok(msg);}}Err(error) => {println!("{}", error);}}}}}
let scope = read_single_line("optional scope: ")?;let description = read_single_line("description: ")?;let body = read_multi_line("optional body:")?;let breaking_change = read_single_line("optional BREAKING CHANGE: ")?;let issues = read_single_line("optional issues (e.g. #2, #8): ")?;
let r#type = match (self.feat,self.fix,self.build,self.chore,self.ci,self.docs,self.style,self.refactor,self.perf,self.test,) {(true, false, false, false, false, false, false, false, false, false) => {Some("feat".to_string())}(false, true, false, false, false, false, false, false, false, false) => {Some("fix".to_string())}(false, false, true, false, false, false, false, false, false, false) => {Some("build".to_string())}(false, false, false, true, false, false, false, false, false, false) => {Some("chore".to_string())}(false, false, false, false, true, false, false, false, false, false) => {Some("ci".to_string())}(false, false, false, false, false, true, false, false, false, false) => {Some("docs".to_string())}(false, false, false, false, false, false, true, false, false, false) => {Some("style".to_string())}(false, false, false, false, false, false, false, true, false, false) => {Some("refactor".to_string())}(false, false, false, false, false, false, false, false, true, false) => {Some("perf".to_string())}(false, false, false, false, false, false, false, false, false, true) => {Some("test".to_string())}_ => None,};
#[derive(Debug)]pub enum ParseError {NoType,NoDescription,EmptyCommitMessage,InvalidFirstLine,}impl fmt::Display for ParseError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {match self {ParseError::NoType => write!(f, "missing type"),ParseError::NoDescription => write!(f, "missing description"),ParseError::EmptyCommitMessage => write!(f, "empty commit message"),ParseError::InvalidFirstLine => write!(f,"first line doesn't match `<type>[optional scope]: <description>`"),}}}impl std::error::Error for ParseError {}
Type {r#type: "build".into(),section: "Other".into(),hidden: true,},Type {r#type: "chore".into(),section: "Other".into(),hidden: true,},Type {r#type: "ci".into(),section: "Other".into(),hidden: true,},Type {r#type: "docs".into(),section: "Documentation".into(),hidden: true,},Type {r#type: "style".into(),section: "Other".into(),hidden: true,},Type {r#type: "refactor".into(),section: "Other".into(),hidden: true,},Type {r#type: "perf".into(),section: "Other".into(),hidden: true,},Type {r#type: "test".into(),section: "Other".into(),hidden: true,},