#[cfg(feature = "miette")]
use miette::Diagnostic;
use relative_path::RelativePathBuf;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value, from_str, to_string};
use thiserror::Error;
use crate::{
action::Action,
semver::{self, Version},
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PubSpec {
raw: String,
parsed: Yaml,
path: RelativePathBuf,
diff: Option<String>,
}
impl PubSpec {
pub(crate) fn new(path: RelativePathBuf, content: String) -> Result<Self, Error> {
match from_str(&content) {
Ok(parsed) => Ok(PubSpec {
raw: content,
parsed,
path,
diff: None,
}),
Err(err) => Err(Error::Deserialize { path, source: err }),
}
}
pub(crate) fn get_version(&self) -> &Version {
&self.parsed.version
}
pub(crate) fn get_path(&self) -> &RelativePathBuf {
&self.path
}
pub(crate) fn set_version(mut self, new_version: &Version) -> serde_yaml::Result<Self> {
let version_line = self.raw.lines().find(|line| line.starts_with("version: "));
self.raw = if let Some(version_line) = version_line {
self.raw.replace(
version_line,
to_string(&Yaml {
version: new_version.clone(),
})?
.trim(),
)
} else {
let mut yaml = from_str::<Mapping>(&self.raw)?;
yaml.insert(
Value::String("version".to_string()),
Value::String(new_version.to_string()),
);
to_string(&yaml)?
};
self.diff = Some(new_version.to_string());
Ok(self)
}
pub(crate) fn write(self) -> Option<Action> {
self.diff.map(|diff| Action::WriteToFile {
content: self.raw,
path: self.path,
diff,
})
}
}
#[derive(Debug, Error)]
#[cfg_attr(feature = "miette", derive(Diagnostic))]
pub enum Error {
#[error("Error deserializing {path}: {source}")]
#[cfg_attr(
feature = "miette",
diagnostic(
code(pubspec_yaml::deserialize),
help(
"knope expects the pubspec.yaml file to be an object with a top level `version` property"
),
url("https://knope.tech/reference/config-file/packages/#pubspecyaml")
)
)]
Deserialize {
path: RelativePathBuf,
#[source]
source: serde_yaml::Error,
},
#[error(transparent)]
#[cfg_attr(feature = "miette", diagnostic(transparent))]
Version(#[from] semver::Error),
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
struct Yaml {
version: Version,
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use std::str::FromStr;
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_get_version() {
let content =
include_str!("../../../knope/tests/prepare_release/pubspec_yaml/in/pubspec.yaml");
assert_eq!(
PubSpec::new(RelativePathBuf::new(), content.to_string())
.unwrap()
.get_version(),
&Version::from_str("1.0.0").unwrap()
);
}
#[test]
fn test_set_version() {
let content =
include_str!("../../../knope/tests/prepare_release/pubspec_yaml/in/pubspec.yaml");
let action = PubSpec::new(RelativePathBuf::from("blah/blah"), content.to_string())
.unwrap()
.set_version(&Version::from_str("1.2.3-rc.4").unwrap())
.unwrap()
.write()
.expect("diff to write");
let expected_content = content.replace("1.0.0", "1.2.3-rc.4");
let expected = Action::WriteToFile {
path: RelativePathBuf::from("blah/blah"),
content: expected_content,
diff: "1.2.3-rc.4".to_string(),
};
assert_eq!(expected, action);
}
}