update_version/parsers/
mod.rs1use anyhow::Result;
2use log::{debug, info};
3use semver::Version;
4use std::path::{Path, PathBuf};
5use thiserror::Error;
6
7pub mod package_json_parser;
8pub mod tauri_config_parser;
9pub mod toml_parser;
10
11#[derive(Debug, Error)]
12#[non_exhaustive]
13pub enum ParsingError {
14 #[error("No versions found in directory: {0}")]
15 NoVersionFoundError(String),
16}
17
18#[derive(Debug, Clone, Default)]
20pub struct WalkOptions {
21 pub no_ignore: bool,
24}
25
26pub fn increment_semver(version: &Version) -> Result<Version> {
32 let mut next = version.clone();
33 next.build = semver::BuildMetadata::EMPTY;
34
35 if version.pre.is_empty() {
36 next.patch += 1;
37 return Ok(next);
38 }
39
40 let pre_str = version.pre.as_str();
42 let parts: Vec<&str> = pre_str.split('.').collect();
43
44 if let Some(last) = parts.last() {
46 if let Ok(n) = last.parse::<u64>() {
47 let prefix = &parts[..parts.len() - 1];
49 let new_pre = if prefix.is_empty() {
50 format!("{}", n + 1)
51 } else {
52 format!("{}.{}", prefix.join("."), n + 1)
53 };
54 next.pre = semver::Prerelease::new(&new_pre)?;
55 return Ok(next);
56 }
57 }
58
59 next.patch += 1;
61 Ok(next)
62}
63
64pub trait Parser {
65 fn update_version(
66 path: impl AsRef<Path>,
67 version: &Version,
68 options: &WalkOptions,
69 ) -> Result<Vec<PathBuf>> {
70 info!("Updating version to {}", version);
71 let files = Self::get_matching_files(path, options)?;
72 let version_regex = Self::version_match_regex()?;
73 for file in &files {
74 debug!("Checking file: '{}'", file.display());
75 let contents = std::fs::read_to_string(file)?;
76 let new_contents = version_regex
77 .replace(contents.as_str(), Self::version_line_format(version)?)
78 .to_string();
79 std::fs::write(file, new_contents)?;
80 }
81 Ok(files)
82 }
83 fn increment_version(path: impl AsRef<Path>, options: &WalkOptions) -> Result<Vec<PathBuf>> {
84 let path = path.as_ref();
85 let current_version = Self::get_current_version(path, options)?;
86 let new_version = increment_semver(¤t_version)?;
87 debug!(
88 "Incrementing version from {} -> {}",
89 current_version, new_version
90 );
91 Self::update_version(path, &new_version, options)
92 }
93 fn get_current_version(path: impl AsRef<Path>, options: &WalkOptions) -> Result<Version> {
94 let path = path.as_ref();
95 let files = Self::get_matching_files(path, options)?;
96 let version_regex = Self::version_match_regex()?;
97
98 for file in files {
99 let contents = std::fs::read_to_string(file)?;
100 if let Some(captures) = version_regex.captures(contents.as_str())
101 && let Some(version) = captures.get(2)
102 {
103 let version = version.as_str();
104 debug!("Found current version: {}", version);
105 return Ok(Version::parse(version)?);
106 }
107 }
108
109 Err(ParsingError::NoVersionFoundError(path.to_string_lossy().to_string()).into())
110 }
111
112 fn get_matching_files(path: impl AsRef<Path>, options: &WalkOptions) -> Result<Vec<PathBuf>> {
113 debug!("Checking matching files");
114 let mut files: Vec<PathBuf> = vec![];
115 let path = path.as_ref();
116 let filename_regex = Self::filename_match_regex()?;
117
118 let mut builder = ignore::WalkBuilder::new(path);
119
120 if options.no_ignore {
121 builder.git_ignore(false);
124 builder.git_global(false);
125 builder.git_exclude(false);
126 } else {
127 builder.add_custom_ignore_filename(".uvignore");
128 }
129
130 for item in builder.build() {
131 let item = item?;
132 let path = item.path();
133 if filename_regex.is_match(path.to_string_lossy().as_ref()) {
134 files.push(path.to_path_buf());
135 }
136 }
137
138 files.sort_by(|a, b| {
140 a.components().count().cmp(&b.components().count())
141 .then_with(|| a.cmp(b))
142 });
143
144 debug!("Found files: {:?}", files);
145 Ok(files)
146 }
147
148 fn version_match_regex() -> Result<regex::Regex>;
149 fn filename_match_regex() -> Result<regex::Regex>;
150 fn version_line_format(version: &Version) -> Result<String>;
151}