Skip to main content

cargo_uv/manifest/
toml_file.rs

1use std::{
2    marker::PhantomData,
3    panic::Location,
4    path::{Path, PathBuf},
5};
6
7use miette::{IntoDiagnostic, bail};
8use semver::Version;
9use toml_edit::DocumentMut;
10use tracing::instrument;
11
12use crate::{
13    VersionLocation,
14    manifest::error::{CargoFileError, CargoFileErrorKind, VersionlocationError},
15};
16
17/// Indicator that the cargo file has been read.
18#[derive(Debug, Clone, Hash, PartialEq, Eq)]
19pub struct ReadToml;
20
21/// Limits what can be done until the file has been read.
22#[derive(Debug)]
23pub struct UnreadToml;
24
25#[derive(Clone)]
26pub struct CargoFile<State> {
27    path: PathBuf,
28    contents: Option<DocumentMut>,
29    __state: PhantomData<State>,
30}
31
32impl<State: std::fmt::Debug> std::fmt::Debug for CargoFile<State> {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.debug_struct("CargoFile")
35            .field("path", &self.path)
36            .field("__state", &self.__state)
37            .finish()
38    }
39}
40
41impl<State: std::hash::Hash> std::hash::Hash for CargoFile<State> {
42    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
43        self.path.hash(state);
44        self.__state.hash(state);
45    }
46}
47
48impl<State: Eq> Eq for CargoFile<State> {}
49
50impl<State: PartialEq> PartialEq for CargoFile<State> {
51    fn eq(&self, other: &Self) -> bool {
52        self.path == other.path && self.__state == other.__state
53    }
54}
55
56impl<'a, S> CargoFile<S> {
57    pub fn path(&'a self) -> &'a Path {
58        &self.path
59    }
60}
61
62impl CargoFile<UnreadToml> {
63    #[instrument]
64    #[track_caller]
65    pub fn new(path: impl Into<PathBuf> + std::fmt::Debug) -> miette::Result<CargoFile<ReadToml>> {
66        let ret: CargoFile<UnreadToml> = Self::new_lazy(path.into());
67        let ret: CargoFile<ReadToml> = ret.read_file()?;
68        tracing::trace!("New Cargo file: {}", ret.path().display());
69        Ok(ret)
70    }
71
72    pub fn new_lazy(path: impl Into<PathBuf>) -> CargoFile<UnreadToml> {
73        Self {
74            path: path.into(),
75            contents: None,
76            __state: PhantomData::<UnreadToml>,
77        }
78    }
79
80    #[instrument(skip(self), fields(self.path))]
81    #[track_caller]
82    pub fn read_file(self) -> miette::Result<CargoFile<ReadToml>> {
83        let CargoFile { path, .. } = self;
84        let contents = match ::std::fs::read_to_string(&path) {
85            Ok(contents) => contents,
86            Err(e) => {
87                let msg = format!("Failed to read to string: {} - {}", e, path.display());
88                tracing::error!(msg);
89                let loc = Location::caller().to_string();
90                tracing::error!(loc);
91                bail!(msg)
92            }
93        };
94
95        let contents = Some(contents.parse::<DocumentMut>().into_diagnostic()?);
96        Ok(CargoFile {
97            path,
98            contents,
99            __state: PhantomData::<ReadToml>,
100        })
101    }
102}
103
104impl CargoFile<ReadToml> {
105    pub fn contents(&self) -> Option<&DocumentMut> {
106        self.contents.as_ref()
107    }
108    pub fn contents_mut(&mut self) -> Option<&mut DocumentMut> {
109        self.contents.as_mut()
110    }
111    #[instrument(skip_all)]
112    pub fn get_package_version(&self) -> Option<Version> {
113        VersionLocation::Package.get_version(&self).ok()
114    }
115    #[instrument(skip_all)]
116    pub fn get_workspace_version(&self) -> Option<Version> {
117        VersionLocation::WorkspacePackage.get_version(&self).ok()
118    }
119
120    #[track_caller]
121    #[instrument(skip(self))]
122    pub fn set_package_version(
123        &mut self,
124        new_version: &Version,
125    ) -> miette::Result<(), CargoFileError> {
126        VersionLocation::Package
127            .set_version(self, new_version)
128            .map_err(|e: VersionlocationError| {
129                let path = (&e).path().to_path_buf();
130                CargoFileErrorKind::LocationError(e).to_error(path)
131            })
132    }
133
134    #[track_caller]
135    #[instrument(skip(self))]
136    pub fn set_workspace_version(
137        &mut self,
138        new_version: &Version,
139    ) -> miette::Result<(), CargoFileError> {
140        VersionLocation::WorkspacePackage
141            .set_version(self, new_version)
142            .map_err(|e: VersionlocationError| {
143                let path = (&e).path().to_path_buf();
144                CargoFileErrorKind::LocationError(e).to_error(path)
145            })
146    }
147
148    #[track_caller]
149    #[instrument(skip(self))]
150    pub fn set_version(&mut self, new_version: Version) -> miette::Result<(), CargoFileError> {
151        let cargo_path = self.path().to_path_buf();
152        let pack_err = VersionLocation::Package.set_version(self, &new_version);
153        let ws_err = VersionLocation::WorkspacePackage.set_version(self, &new_version);
154
155        if let Some(cargo_file_err) = pack_err.err() {
156            use crate::manifest::error::VersionLocationErrorKind as VerLocErrKind;
157            match &cargo_file_err.kind() {
158                VerLocErrKind::SetByWorkspace => (),
159                VerLocErrKind::PackageNotFound => (),
160                VerLocErrKind::NotFound(_) => (),
161                VerLocErrKind::WorkspaceNotFound => unreachable!("Setting package"),
162                VerLocErrKind::ItemInvalid(_) | VerLocErrKind::SemverError(_) => {
163                    return Err(
164                        CargoFileErrorKind::LocationError(cargo_file_err).to_error(cargo_path)
165                    );
166                }
167            }
168        } else {
169            return Ok(());
170        };
171
172        if let Some(ver_loc_error) = ws_err.err() {
173            use crate::manifest::error::VersionLocationErrorKind as VerLocErrKind;
174            match ver_loc_error.kind() {
175                VerLocErrKind::SetByWorkspace => unreachable!(),
176                VerLocErrKind::NotFound(_) => {
177                    Err(CargoFileErrorKind::NoPackageOrWorkspaceVersion.to_error(cargo_path))
178                }
179                VerLocErrKind::PackageNotFound => unreachable!(),
180                VerLocErrKind::WorkspaceNotFound => {
181                    Err(CargoFileErrorKind::NoPackageOrWorkspaceVersion.to_error(cargo_path))
182                }
183                VerLocErrKind::ItemInvalid(_) | VerLocErrKind::SemverError(_) => {
184                    Err(CargoFileErrorKind::LocationError(ver_loc_error).to_error(cargo_path))
185                }
186            }
187        } else {
188            Ok(())
189        }
190    }
191
192    #[instrument(skip(self))]
193    pub fn write_cargo_file(&mut self) -> miette::Result<()> {
194        let contents = self.contents.as_ref().unwrap().to_string();
195        std::fs::write(&self.path, contents).into_diagnostic()?;
196        Ok(())
197    }
198}