1use std::{fmt::Debug, path::PathBuf};
2
3use cargo::Cargo;
4pub use go_mod::{GoMod, GoVersioning};
5use maven_pom::MavenPom;
6use package_json::PackageJson;
7use pubspec::PubSpec;
8use pyproject::PyProject;
9use relative_path::RelativePathBuf;
10use serde::{Serialize, Serializer};
11use tauri_conf_json::TauriConfJson;
12
13use crate::{
14 Action,
15 action::ActionSet::{Single, Two},
16 semver::Version,
17 versioned_file::{cargo_lock::CargoLock, gleam::Gleam},
18};
19
20pub mod cargo;
21mod cargo_lock;
22mod gleam;
23mod go_mod;
24mod maven_pom;
25mod package_json;
26mod pubspec;
27mod pyproject;
28mod tauri_conf_json;
29
30#[derive(Clone, Debug)]
31pub enum VersionedFile {
32 Cargo(Cargo),
33 CargoLock(CargoLock),
34 PubSpec(PubSpec),
35 Gleam(Gleam),
36 GoMod(GoMod),
37 PackageJson(PackageJson),
38 PyProject(PyProject),
39 MavenPom(MavenPom),
40 TauriConf(TauriConfJson),
41 TauriMacosConf(TauriConfJson),
42 TauriWindowsConf(TauriConfJson),
43 TauriLinuxConf(TauriConfJson),
44}
45
46impl VersionedFile {
47 pub fn new<S: AsRef<str> + Debug>(
54 config: &Config,
55 content: String,
56 git_tags: &[S],
57 ) -> Result<Self, Error> {
58 match config.format {
59 Format::Cargo => Cargo::new(config.as_path(), &content)
60 .map(VersionedFile::Cargo)
61 .map_err(Error::Cargo),
62 Format::CargoLock => CargoLock::new(config.as_path(), &content)
63 .map(VersionedFile::CargoLock)
64 .map_err(Error::CargoLock),
65 Format::PyProject => PyProject::new(config.as_path(), content)
66 .map(VersionedFile::PyProject)
67 .map_err(Error::PyProject),
68 Format::PubSpec => PubSpec::new(config.as_path(), content)
69 .map(VersionedFile::PubSpec)
70 .map_err(Error::PubSpec),
71 Format::Gleam => Gleam::new(config.as_path(), &content)
72 .map(VersionedFile::Gleam)
73 .map_err(Error::Gleam),
74 Format::GoMod => GoMod::new(config.as_path(), content, git_tags)
75 .map(VersionedFile::GoMod)
76 .map_err(Error::GoMod),
77 Format::PackageJson => PackageJson::new(config.as_path(), content)
78 .map(VersionedFile::PackageJson)
79 .map_err(Error::PackageJson),
80 Format::MavenPom => MavenPom::new(config.as_path(), content)
81 .map(VersionedFile::MavenPom)
82 .map_err(Error::MavenPom),
83 Format::TauriConf => TauriConfJson::new(config.as_path(), content)
84 .map(VersionedFile::TauriConf)
85 .map_err(Error::TauriConfJson),
86 }
87 }
88
89 #[must_use]
90 pub fn path(&self) -> &RelativePathBuf {
91 match self {
92 VersionedFile::Cargo(cargo) => &cargo.path,
93 VersionedFile::CargoLock(cargo_lock) => &cargo_lock.path,
94 VersionedFile::PyProject(pyproject) => &pyproject.path,
95 VersionedFile::PubSpec(pubspec) => pubspec.get_path(),
96 VersionedFile::Gleam(gleam) => &gleam.path,
97 VersionedFile::GoMod(gomod) => gomod.get_path(),
98 VersionedFile::PackageJson(package_json) => package_json.get_path(),
99 VersionedFile::MavenPom(maven_pom) => &maven_pom.path,
100 VersionedFile::TauriConf(tauri_conf)
101 | VersionedFile::TauriMacosConf(tauri_conf)
102 | VersionedFile::TauriWindowsConf(tauri_conf)
103 | VersionedFile::TauriLinuxConf(tauri_conf) => tauri_conf.get_path(),
104 }
105 }
106
107 pub fn version(&self) -> Result<Version, Error> {
113 match self {
114 VersionedFile::Cargo(cargo) => cargo.get_version().map_err(Error::Cargo),
115 VersionedFile::CargoLock(_) => Err(Error::NoVersion),
116 VersionedFile::PyProject(pyproject) => Ok(pyproject.version.clone()),
117 VersionedFile::PubSpec(pubspec) => Ok(pubspec.get_version().clone()),
118 VersionedFile::Gleam(gleam) => Ok(gleam.get_version().map_err(Error::Gleam)?),
119 VersionedFile::GoMod(gomod) => Ok(gomod.get_version().clone()),
120 VersionedFile::PackageJson(package_json) => Ok(package_json.get_version().clone()),
121 VersionedFile::MavenPom(maven_pom) => maven_pom.get_version().map_err(Error::MavenPom),
122 VersionedFile::TauriConf(tauri_conf)
123 | VersionedFile::TauriMacosConf(tauri_conf)
124 | VersionedFile::TauriWindowsConf(tauri_conf)
125 | VersionedFile::TauriLinuxConf(tauri_conf) => Ok(tauri_conf.get_version().clone()),
126 }
127 }
128
129 pub(crate) fn set_version(
135 self,
136 new_version: &Version,
137 dependency: Option<&str>,
138 go_versioning: GoVersioning,
139 ) -> Result<Self, SetError> {
140 match self {
141 Self::Cargo(cargo) => Ok(Self::Cargo(cargo.set_version(new_version, dependency))),
142 Self::CargoLock(cargo_lock) => cargo_lock
143 .set_version(new_version, dependency)
144 .map(Self::CargoLock)
145 .map_err(SetError::CargoLock),
146 Self::PyProject(pyproject) => Ok(Self::PyProject(pyproject.set_version(new_version))),
147 Self::PubSpec(pubspec) => pubspec
148 .set_version(new_version)
149 .map_err(SetError::Yaml)
150 .map(Self::PubSpec),
151 Self::Gleam(gleam) => Ok(Self::Gleam(gleam.set_version(new_version))),
152 Self::GoMod(gomod) => gomod
153 .set_version(new_version.clone(), go_versioning)
154 .map_err(SetError::GoMod)
155 .map(Self::GoMod),
156 Self::PackageJson(package_json) => package_json
157 .set_version(new_version)
158 .map_err(SetError::Json)
159 .map(Self::PackageJson),
160 Self::MavenPom(maven_pom) => maven_pom
161 .set_version(new_version)
162 .map_err(SetError::MavenPom)
163 .map(Self::MavenPom),
164 Self::TauriConf(tauri_conf) => tauri_conf
165 .set_version(new_version)
166 .map_err(SetError::Json)
167 .map(Self::TauriConf),
168 Self::TauriMacosConf(tauri_conf) => tauri_conf
169 .set_version(new_version)
170 .map_err(SetError::Json)
171 .map(Self::TauriMacosConf),
172 Self::TauriWindowsConf(tauri_conf) => tauri_conf
173 .set_version(new_version)
174 .map_err(SetError::Json)
175 .map(Self::TauriWindowsConf),
176 Self::TauriLinuxConf(tauri_conf) => tauri_conf
177 .set_version(new_version)
178 .map_err(SetError::Json)
179 .map(Self::TauriLinuxConf),
180 }
181 }
182
183 pub fn write(self) -> Option<impl IntoIterator<Item = Action>> {
184 match self {
185 Self::Cargo(cargo) => cargo.write().map(Single),
186 Self::CargoLock(cargo_lock) => cargo_lock.write().map(Single),
187 Self::PyProject(pyproject) => pyproject.write().map(Single),
188 Self::PubSpec(pubspec) => pubspec.write().map(Single),
189 Self::Gleam(gleam) => gleam.write().map(Single),
190 Self::GoMod(gomod) => gomod.write().map(Two),
191 Self::PackageJson(package_json) => package_json.write().map(Single),
192 Self::MavenPom(maven_pom) => maven_pom.write().map(Single),
193 Self::TauriConf(tauri_conf)
194 | Self::TauriMacosConf(tauri_conf)
195 | Self::TauriWindowsConf(tauri_conf)
196 | Self::TauriLinuxConf(tauri_conf) => tauri_conf.write().map(Single),
197 }
198 }
199}
200
201#[derive(Debug, thiserror::Error)]
202#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
203pub enum SetError {
204 #[error("Error serializing JSON, this is a bug: {0}")]
205 #[cfg_attr(
206 feature = "miette",
207 diagnostic(
208 code(knope_versioning::versioned_file::json_serialize),
209 help("This is a bug in knope, please report it."),
210 url("https://github.com/knope-dev/knope/issues")
211 )
212 )]
213 Json(#[from] serde_json::Error),
214 #[error("Error serializing YAML, this is a bug: {0}")]
215 #[cfg_attr(
216 feature = "miette",
217 diagnostic(
218 code(knope_versioning::versioned_file::yaml_serialize),
219 help("This is a bug in knope, please report it."),
220 url("https://github.com/knope-dev/knope/issues"),
221 )
222 )]
223 Yaml(#[from] serde_yaml::Error),
224 #[error(transparent)]
225 #[cfg_attr(feature = "miette", diagnostic(transparent))]
226 GoMod(#[from] go_mod::SetError),
227 #[error(transparent)]
228 #[cfg_attr(feature = "miette", diagnostic(transparent))]
229 CargoLock(#[from] cargo_lock::SetError),
230 #[error(transparent)]
231 #[cfg_attr(feature = "miette", diagnostic(transparent))]
232 MavenPom(#[from] maven_pom::Error),
233}
234
235#[derive(Debug, thiserror::Error)]
236#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
237pub enum Error {
238 #[error("This file can't contain a version")]
239 #[cfg_attr(
240 feature = "miette",
241 diagnostic(
242 code(knope_versioning::versioned_file::no_version),
243 help("This is likely a bug, please report it."),
244 url("https://github.com/knope-dev/knope/issues")
245 )
246 )]
247 NoVersion,
248 #[error(transparent)]
249 #[cfg_attr(feature = "miette", diagnostic(transparent))]
250 Cargo(#[from] cargo::Error),
251 #[error(transparent)]
252 #[cfg_attr(feature = "miette", diagnostic(transparent))]
253 CargoLock(#[from] cargo_lock::Error),
254 #[error(transparent)]
255 #[cfg_attr(feature = "miette", diagnostic(transparent))]
256 PyProject(#[from] pyproject::Error),
257 #[error(transparent)]
258 #[cfg_attr(feature = "miette", diagnostic(transparent))]
259 PubSpec(#[from] pubspec::Error),
260 #[error(transparent)]
261 #[cfg_attr(feature = "miette", diagnostic(transparent))]
262 Gleam(#[from] gleam::Error),
263 #[error(transparent)]
264 #[cfg_attr(feature = "miette", diagnostic(transparent))]
265 GoMod(#[from] go_mod::Error),
266 #[error(transparent)]
267 #[cfg_attr(feature = "miette", diagnostic(transparent))]
268 PackageJson(#[from] package_json::Error),
269 #[error(transparent)]
270 #[cfg_attr(feature = "miette", diagnostic(transparent))]
271 MavenPom(#[from] maven_pom::Error),
272 #[error(transparent)]
273 #[cfg_attr(feature = "miette", diagnostic(transparent))]
274 TauriConfJson(#[from] tauri_conf_json::Error),
275}
276
277#[derive(Clone, Copy, Debug, Eq, PartialEq)]
281pub(crate) enum Format {
282 Cargo,
283 CargoLock,
284 PyProject,
285 PubSpec,
286 Gleam,
287 GoMod,
288 PackageJson,
289 MavenPom,
290 TauriConf,
291}
292
293impl Format {
294 const FILE_NAMES: &'static [(&'static str, Self)] = &[
296 ("Cargo.toml", Format::Cargo),
297 ("Cargo.lock", Format::CargoLock),
298 ("gleam.toml", Format::Gleam),
299 ("go.mod", Format::GoMod),
300 ("package.json", Format::PackageJson),
301 ("pom.xml", Format::MavenPom),
302 ("pubspec.yaml", Format::PubSpec),
303 ("pyproject.toml", Format::PyProject),
304 ("tauri.conf.json", Format::TauriConf),
305 ("tauri.macos.conf.json", Format::TauriConf),
306 ("tauri.windows.conf.json", Format::TauriConf),
307 ("tauri.linux.conf.json", Format::TauriConf),
308 ];
309
310 fn try_from(file_name: &str) -> Option<Self> {
311 Self::FILE_NAMES
312 .iter()
313 .find(|(name, _)| file_name == *name)
314 .map(|(_, format)| *format)
315 }
316}
317
318#[derive(Debug, thiserror::Error)]
319#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
320#[error("Unknown file: {path}")]
321#[cfg_attr(
322 feature = "miette",
323 diagnostic(
324 code(knope_versioning::versioned_file::unknown_file),
325 help("Knope identities the type of file based on its name."),
326 url("https://knope.tech/reference/config-file/packages#versioned_files")
327 )
328)]
329pub struct UnknownFile {
330 pub path: RelativePathBuf,
331}
332
333#[derive(Clone, Debug, Eq, PartialEq)]
335pub struct Config {
336 pub(crate) path: RelativePathBuf,
338 pub(crate) format: Format,
340 pub dependency: Option<String>,
342}
343
344impl Config {
345 pub fn new(path: RelativePathBuf, dependency: Option<String>) -> Result<Self, UnknownFile> {
351 let Some(file_name) = path.file_name() else {
352 return Err(UnknownFile { path });
353 };
354 let Some(format) = Format::try_from(file_name) else {
355 return Err(UnknownFile { path });
356 };
357 Ok(Config {
358 path,
359 format,
360 dependency,
361 })
362 }
363
364 #[must_use]
365 pub fn as_path(&self) -> RelativePathBuf {
366 self.path.clone()
367 }
368
369 #[must_use]
370 pub fn to_pathbuf(&self) -> PathBuf {
371 self.as_path().to_path("")
372 }
373
374 pub fn defaults() -> impl Iterator<Item = Self> {
375 Format::FILE_NAMES
376 .iter()
377 .copied()
378 .map(|(name, format)| Self {
379 format,
380 path: RelativePathBuf::from(name),
381 dependency: None,
382 })
383 }
384}
385
386impl Serialize for Config {
387 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
388 where
389 S: Serializer,
390 {
391 self.as_path().serialize(serializer)
392 }
393}
394
395impl From<&Config> for PathBuf {
396 fn from(path: &Config) -> Self {
397 path.as_path().to_path("")
398 }
399}
400
401impl PartialEq<RelativePathBuf> for Config {
402 fn eq(&self, other: &RelativePathBuf) -> bool {
403 self.path == *other
404 }
405}
406
407impl PartialEq<Config> for RelativePathBuf {
408 fn eq(&self, other: &Config) -> bool {
409 other == self
410 }
411}