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