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