mod bump;
mod error;
mod kind;
pub mod lockfile;
pub mod manifest;
use std::borrow::Borrow;
use std::str::FromStr;
use bytes::Bytes;
use either::Either;
use relative_path::{RelativePath, RelativePathBuf};
use semver::Version;
use tracing::info;
use url::Url;
use crate::changelog::Changelog;
use crate::project::Project;
use crate::repository::adapters::subdirectory::Subdirectory;
use crate::repository::types::staging::Staging;
use crate::repository::{Remote, Repository, Stage};
pub use self::bump::{Bump, BumpOrVersion, Error as BumpError};
pub use self::error::Error;
pub use self::kind::PackageKind;
pub use self::lockfile::Lockfile;
pub use self::manifest::Manifest;
use self::manifest::{Dependencies, DependenciesMut, DependencyMut, DependencyRef};
#[derive(Clone)]
pub struct Package<T = Staging> {
pub(crate) repository: Subdirectory<T>,
manifest: Manifest,
primary: bool,
}
impl Package {
pub fn new_cargo(name: impl Into<String>) -> Self {
Self {
repository: Subdirectory::new_root(Staging::new()),
manifest: Manifest::new_cargo(name),
primary: false,
}
}
}
impl<T> Package<T> {
pub fn name(&self) -> &str {
match self.manifest() {
Manifest::Cargo(cargo) => cargo.package().expect("package").name(),
}
}
pub fn description(&self) -> Option<&str> {
match self.manifest() {
Manifest::Cargo(cargo) => cargo.package().expect("package").description(),
}
}
pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
match self.manifest_mut() {
Manifest::Cargo(cargo) => {
cargo
.package_mut()
.expect("package")
.set_description(description);
}
}
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.set_description(description);
self
}
pub fn version(&self) -> Version {
match self.manifest() {
Manifest::Cargo(cargo) => cargo.package().expect("package").version(),
}
}
pub fn set_version(&mut self, version: impl Into<Version>) -> &mut Self {
match self.manifest_mut() {
Manifest::Cargo(cargo) => cargo.package_mut().expect("package").set_version(version),
};
self
}
pub fn bump_version(&mut self, bump: impl Into<Bump>) -> Result<&mut Self, BumpError> {
let mut version = self.version();
bump.into().bump(&mut version)?;
self.set_version(version);
Ok(self)
}
pub fn with_version(mut self, version: impl Into<Version>) -> Self {
self.set_version(version);
self
}
pub fn repository(&self) -> Option<Url> {
match self.manifest() {
Manifest::Cargo(cargo) => cargo.package().expect("package").repository(),
}
}
pub fn set_repository(&mut self, repository: impl Into<Url>) -> &mut Self {
match self.manifest_mut() {
Manifest::Cargo(cargo) => {
cargo
.package_mut()
.expect("package")
.set_repository(repository);
}
}
self
}
pub fn with_repository(mut self, repository: impl Into<Url>) -> Self {
self.set_repository(repository);
self
}
pub fn authors(&self) -> Option<impl IntoIterator<Item = &str>> {
match self.manifest() {
Manifest::Cargo(cargo) => cargo.package().expect("package").authors(),
}
}
pub fn add_author(&mut self, author: impl Into<String>) -> &mut Self {
match self.manifest_mut() {
Manifest::Cargo(cargo) => {
cargo.package_mut().expect("package").add_author(author);
}
}
self
}
pub fn with_author(mut self, author: impl Into<String>) -> Self {
self.add_author(author);
self
}
pub fn path(&self) -> &RelativePath {
self.repository.path()
}
pub fn manifest_path(&self) -> RelativePathBuf {
self.path().join(self.kind().file_name())
}
pub fn kind(&self) -> PackageKind {
self.manifest.package_kind()
}
pub fn is_primary(&self) -> bool {
self.primary
}
}
impl<T> Package<T> {
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
pub fn manifest_mut(&mut self) -> &mut Manifest {
&mut self.manifest
}
}
impl<T> Package<T>
where
T: Repository,
{
pub fn changelog(&self) -> Option<Changelog> {
self.get_file_as("CHANGELOG.md").ok().flatten()
}
}
impl<T> Package<T>
where
T: Stage,
{
pub fn add_file(
&mut self,
path: impl Into<RelativePathBuf>,
file: impl Into<Bytes>,
) -> Result<&mut Self, Error<T::Error>> {
self.repository
.add_file(path, file)
.map_err(Error::Repository)?;
Ok(self)
}
pub fn with_file(
mut self,
path: impl Into<RelativePathBuf>,
file: impl Into<Bytes>,
) -> Result<Self, Error<T::Error>> {
self.add_file(path, file)?;
Ok(self)
}
}
impl<T> Package<T>
where
T: Repository,
{
pub fn get_file(
&self,
path: impl AsRef<RelativePath>,
) -> Result<Option<Bytes>, Error<T::Error>> {
if path.as_ref() == self.kind().file_name() {
return Ok(Some(self.manifest.to_string().into()));
}
self.repository.get_file(path).map_err(Error::Repository)
}
#[allow(clippy::type_complexity)]
pub fn get_file_as<U>(
&self,
path: impl AsRef<RelativePath>,
) -> Result<Option<U>, Either<Error<T::Error>, U::Err>>
where
U: FromStr,
{
match self.get_file(path).map_err(Either::Left)? {
Some(bytes) => match std::str::from_utf8(&bytes) {
Ok(str) => str.parse().map(Some).map_err(Either::Right),
Err(err) => Err(Either::Left(Error::Utf8(err))),
},
None => Ok(None),
}
}
}
impl<T> Package<T> {
pub fn get_dependency(&self, name: impl AsRef<str>) -> Option<DependencyRef<'_>> {
self.manifest().get_dependency(name)
}
pub fn get_dependency_mut(&mut self, name: impl AsRef<str>) -> Option<DependencyMut<'_>> {
self.manifest_mut().get_dependency_mut(name)
}
pub fn dependencies(&self) -> Dependencies<'_> {
self.manifest().dependencies()
}
pub fn dependencies_mut(&mut self) -> DependenciesMut<'_> {
self.manifest_mut().dependencies_mut()
}
}
impl<T> Package<T> {
pub fn get_dev_dependency(&self, name: impl AsRef<str>) -> Option<DependencyRef<'_>> {
self.manifest().get_dev_dependency(name)
}
pub fn get_dev_dependency_mut(&mut self, name: impl AsRef<str>) -> Option<DependencyMut<'_>> {
self.manifest_mut().get_dev_dependency_mut(name)
}
pub fn dev_dependencies(&self) -> Dependencies<'_> {
self.manifest().dev_dependencies()
}
pub fn dev_dependencies_mut(&mut self) -> DependenciesMut<'_> {
self.manifest_mut().dev_dependencies_mut()
}
}
impl<T> Package<T> {
pub fn get_build_dependency(&self, name: impl AsRef<str>) -> Option<DependencyRef<'_>> {
self.manifest().get_build_dependency(name)
}
pub fn get_build_dependency_mut(&mut self, name: impl AsRef<str>) -> Option<DependencyMut<'_>> {
self.manifest_mut().get_build_dependency_mut(name)
}
pub fn build_dependencies(&self) -> Dependencies<'_> {
self.manifest().build_dependencies()
}
pub fn build_dependencies_mut(&mut self) -> DependenciesMut<'_> {
self.manifest_mut().build_dependencies_mut()
}
}
impl<T> Package<&T>
where
T: Clone,
{
pub fn detached(self) -> Package<T> {
Package {
repository: self.repository.detached(),
manifest: self.manifest,
primary: self.primary,
}
}
}
impl<T> Package<&mut T>
where
T: Clone,
{
pub fn detached(self) -> Package<T> {
Package {
repository: self.repository.detached(),
manifest: self.manifest,
primary: self.primary,
}
}
}
impl<T> Package<T>
where
T: Remote,
{
pub fn request_release(&self, version: impl Into<BumpOrVersion>) -> Result<(), T::Error> {
let version = version.into();
info!(
package = self.name(),
version = %self.version(),
request = %version,
"Requesting release"
);
self.repository
.inner()
.request_package_release(self.name(), version)?;
Ok(())
}
pub fn build_release_notes(
&self,
version: impl Borrow<Version>,
) -> Result<crate::changelog::Release, T::Error> {
self.repository.inner().get_changelog_release(
self.name(),
version.borrow(),
self.is_primary(),
)
}
}
impl<T> Package<T>
where
T: Repository,
{
pub(super) fn from_manifest(
project: &Project<T>,
path: impl Into<RelativePathBuf>,
manifest: Manifest,
) -> Option<Package<&T>> {
let kind = manifest.package_kind();
let primary = match kind {
PackageKind::Cargo => {
let pkg = manifest.try_as_cargo_ref()?.package()?;
pkg.name() == project.name()
}
};
Some(Package {
repository: Subdirectory::new_unvalidated(&project.repository, path.into()),
manifest: manifest.clone(),
primary,
})
}
}
#[cfg(test)]
mod tests {
use semver::Version;
use super::{Package, PackageKind};
#[test]
fn test_package_builder() {
let mut package = Package::new_cargo("example");
assert_eq!(package.name(), "example");
assert_eq!(package.description(), None);
assert_eq!(package.version().to_string(), "0.0.0");
assert_eq!(package.dependencies().into_iter().count(), 0);
assert_eq!(package.dev_dependencies().into_iter().count(), 0);
assert_eq!(package.build_dependencies().into_iter().count(), 0);
assert_eq!(package.kind(), PackageKind::Cargo);
assert_eq!(package.path(), "");
package.set_version("0.1.0".parse::<Version>().unwrap());
assert_eq!(package.version().to_string(), "0.1.0");
assert_eq!(package.changelog(), None);
package.add_file("hello-world.txt", "Hello World!").unwrap();
let txt = package.get_file("hello-world.txt").unwrap();
assert_eq!(txt, Some("Hello World!".into()));
}
}