use crate::Location;
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Dependency {
pub name: String,
pub location: Location,
pub path: Option<PathBuf>,
pub edition: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git: Option<GitSource>,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct GitSource {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rev: Option<String>,
}
impl GitSource {
pub fn reference(&self) -> Result<GitReference, &'static str> {
GitReference::from_opts(&self.branch, &self.tag, &self.rev)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum GitReference {
Branch(String),
Tag(String),
Rev(String),
DefaultBranch,
}
impl GitReference {
pub fn is_mutable(&self) -> bool {
matches!(self, GitReference::Branch(_) | GitReference::DefaultBranch)
}
pub fn lock_string(&self) -> String {
match self {
GitReference::Branch(b) => format!("branch={b}"),
GitReference::Tag(t) => format!("tag={t}"),
GitReference::Rev(r) => format!("rev={r}"),
GitReference::DefaultBranch => "default".to_string(),
}
}
pub fn from_opts(
branch: &Option<String>,
tag: &Option<String>,
rev: &Option<String>,
) -> Result<GitReference, &'static str> {
match (branch, tag, rev) {
(Some(branch), None, None) => Ok(GitReference::Branch(branch.clone())),
(None, Some(tag), None) => Ok(GitReference::Tag(tag.clone())),
(None, None, Some(rev)) if is_commit_hash(rev) => Ok(GitReference::Rev(rev.clone())),
(None, None, Some(_)) => {
Err("a git `rev` must be a commit hash; use `branch` or `tag` for a named reference")
}
(None, None, None) => Ok(GitReference::DefaultBranch),
_ => Err("a git dependency may specify at most one of `branch`, `tag`, or `rev`"),
}
}
}
fn is_commit_hash(s: &str) -> bool {
(4..=64).contains(&s.len()) && s.bytes().all(|b| b.is_ascii_hexdigit())
}
impl Display for Dependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} (on {:?})", self.name, self.location)?;
if let Some(path) = &self.path {
write!(f, " (at {})", path.display())?;
}
if let Some(edition) = self.edition {
write!(f, " (edition {edition})")?;
}
if let Some(git) = &self.git {
write!(f, " (git {})", git.url)?;
if let Some(branch) = &git.branch {
write!(f, " (branch {branch})")?;
}
if let Some(tag) = &git.tag {
write!(f, " (tag {tag})")?;
}
if let Some(rev) = &git.rev {
write!(f, " (rev {rev})")?;
}
}
Ok(())
}
}