conventional_semver_rs/release/
mod.rs1extern crate custom_error;
2use custom_error::custom_error;
3use std::num::TryFromIntError;
4use std::fs::File;
5use std::io::{self, Write};
6use std::path::Path;
7use git2::{Signature, Oid};
8use regex::Regex;
9use git2::Commit;
10
11use crate::config::ConventionalSemverConfig;
12use crate::ConventionalRepo;
13
14custom_error! { pub Error
15 VersionFileError{source: io::Error, file: String} = "Version file error({file}): {source}.",
16 VersionMatchError{file: String} = "Unable find version in version file {file}",
17 SignatureError{source: TryFromIntError} = "Encountered error when attempting to create git signature timpstamp {source}",
18 GitError{source: git2::Error} = "An error occurred when performing a Git action: {source}",
19}
20
21static SEMVER_MATCHER: &str = r"[vV]?\d+\.\d+\.\d+[-+\w\.]*";
22
23#[derive(Debug)]
24pub struct VersionFile {
25 relative_path: String,
26 matcher: Regex,
27 v: bool,
28}
29impl VersionFile {
30 pub fn new(path: String, version_prefix: String, version_postfix: String, v: bool) -> Result<Self, regex::Error> {
31 let regex = construct_matcher(version_prefix, version_postfix)?;
32 Ok(VersionFile{
33 relative_path: path,
34 matcher: regex,
35 v,
36 })
37 }
38
39 pub fn config_to_version_files(config: &ConventionalSemverConfig) -> anyhow::Result<Vec<VersionFile>> {
40 match &config.version_files {
41 None => Ok(vec![]),
42 Some(version_files) => {
43 version_files.iter().map(|v_file| -> anyhow::Result<VersionFile> {
44 Ok(VersionFile::new(
45 v_file.path.clone(),
46 v_file.version_prefix.as_ref()
47 .unwrap_or(&String::from("")).clone(),
48 v_file.version_postfix.as_ref()
49 .unwrap_or(&String::from("")).clone(),
50 v_file.v
51 )?)
52 }).collect()
53 }
54 }
55 }
56}
57
58fn construct_matcher(prefix: String, postfix: String) -> Result<regex::Regex, regex::Error> {
63 Ok(Regex::new(&format!("({}){}({})", prefix, SEMVER_MATCHER, postfix))?)
64}
65
66pub fn bump_version_files(repo_path: &str, version: &str, files: &Vec<VersionFile>) -> Vec<Error> {
69 let version = match version.strip_prefix("v") {
70 Some(v) => v,
71 None => version,
72 };
73
74 files.iter().filter_map(|f| -> Option<Error> {
75 let str_pth = format!("{}/{}", repo_path, f.relative_path).to_string();
77 let pth = Path::new(&str_pth);
78 let contents = match std::fs::read_to_string(pth) {
79 Ok(c) => c,
80 Err(e) => return Some(Error::VersionFileError{source: e, file: f.relative_path.clone()}),
81 };
82
83 let cap = match f.matcher.captures(&contents) {
85 Some(c) => c,
86 None => return Some(Error::VersionMatchError{file: f.relative_path.clone()}),
87 };
88
89 let fmt_str = match f.v {
90 true => format!("{}v{}{}", cap[1].to_string(), version, cap[2].to_string()),
91 false => format!("{}{}{}", cap[1].to_string(), version, cap[2].to_string()),
92 };
93 let cow = f.matcher.replace_all(&contents, fmt_str);
94
95 match File::options().write(true).open(pth) {
98 Ok(mut out_file) => {
99 out_file.write_all(cow.as_ref().as_bytes()).err()?;
100 },
101 Err(e) => return Some(Error::VersionFileError{source: e, file: f.relative_path.clone()}),
102 }
103 None
104 }).collect()
105}
106
107pub fn tag_release(repo: &ConventionalRepo, version: &str) -> Result<Oid, Error> {
109 let sig = Signature::now(
111 &repo.config.commit_signature.name,
112 &repo.config.commit_signature.email)?;
113 let head = repo.repo.head()?.peel_to_commit()?;
114 Ok(repo.repo.tag(&version.to_string(), head.as_object(), &sig, "", false)?)
115}
116
117pub fn commit_version_files(
118 repo: &ConventionalRepo,
119 version: &str,
120 version_files: &Vec<VersionFile>
121) -> Result<Oid, Error> {
122 let sig = Signature::now(
123 &repo.config.commit_signature.name,
124 &repo.config.commit_signature.email)?;
125
126 let head = repo.repo.head()?;
127 let commit = head.peel_to_commit()?;
128 let parent_commits: [&Commit; 1] = [&commit];
129
130 let mut index = repo.repo.index()?;
131 version_files.iter().for_each(|v: &VersionFile| {
132 if let Err(e) = index.add_path(&Path::new(&v.relative_path)) {
133 eprintln!("Error Encountered {}", e);
134 }
135 });
136 index.write()?;
137
138 let mut index = repo.repo.index()?;
140 let oid = index.write_tree()?;
141 let commit_tree = repo.repo.find_tree(oid)?;
142
143 Ok(repo.repo.commit(
144 Some("HEAD"),
145 &sig,
146 &sig,
147 &format!("chore(release): created release {}", version).to_owned(),
148 &commit_tree,
149 &parent_commits
150 )?)
151}
152