midenup 1.1.0

The Miden toolchain manager
use std::{fmt, hash::Hash, path::PathBuf, time::SystemTime};

pub use semver;
use serde::{Deserialize, Serialize};

/// Used to specify from which  particular revision of a repository.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum GitTarget {
    /// The components is pointing to a specific revision in the repository.
    #[serde(untagged)]
    Revision {
        #[serde(rename = "revision")]
        hash: String,
    },
    /// The components is pointing to a specific tag in the repository.
    #[serde(untagged)]
    Tag {
        #[serde(rename = "tag")]
        name: String,
    },
    /// The components is pointing to a specific *branch* in the repository.
    ///
    /// NOTE: When an update is issued, these type of components will trigger an update if the
    /// branch they were pointing to had new commits since the time the component was installed.
    /// This means that these components are _not_ deterministic and their behavior could change
    /// in-between updates.
    #[serde(untagged)]
    Branch {
        /// This is the name of the branch being tracked.
        #[serde(rename = "branch")]
        name: String,
        /// This field represents the revision hash that is currently presently installed.
        ///
        /// This is only meant to be used in the local manifest in order to check for updates.
        latest_revision: Option<String>,
    },
}
impl Default for GitTarget {
    fn default() -> Self {
        GitTarget::Branch {
            name: String::from("main"),
            latest_revision: None,
        }
    }
}

impl fmt::Display for GitTarget {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self {
            GitTarget::Branch { name, .. } => write!(f, "branch = \"{name}\""),
            GitTarget::Revision { hash } => write!(f, "rev = \"{hash}\""),
            GitTarget::Tag { name: tag } => write!(f, "tag = \"{tag}\""),
        }
    }
}

impl GitTarget {
    pub fn to_cargo_flag(&self) -> [String; 2] {
        match &self {
            GitTarget::Branch { name, .. } => [String::from("--branch"), String::from(name)],
            GitTarget::Revision { hash } => [String::from("--rev"), String::from(hash)],
            GitTarget::Tag { name: tag } => [String::from("--tag"), String::from(tag)],
        }
    }
}

/// Represents the canonical versioning authority for a tool or toolchain
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Authority {
    /// The authority for this tool/toolchain is a local filesystem path
    Path {
        /// The path to the artifact.
        path: PathBuf,
        /// Represents the latest modification done inside this directory.
        #[serde(skip_serializing_if = "Option::is_none")]
        last_modification: Option<SystemTime>,
    },
    /// The authority for this tool/toolchain is a git repository.
    Git {
        /// Points to the git repository containting the [crate::manifest::Component].
        repository_url: String,
        /// The subdirectory within the repository which contains the component
        #[serde(skip_serializing_if = "Option::is_none")]
        subpath: Option<String>,
        /// If the target is missing from the [crate::manifest::Manifest], then we assume that it
        /// is pointing to the tip of the `main` branch
        #[serde(default)]
        #[serde(flatten)]
        target: GitTarget,
    },
    /// The authority for this tool/toolchain is a semantic version resolved in the appropriate
    /// registry (e.g. crates.io).
    Registry {
        /// The semantic versioning string for the package/artifact to fetch
        version: semver::Version,
    },
}

impl core::str::FromStr for Authority {
    type Err = serde_json::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_str(s)
    }
}

impl fmt::Display for Authority {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self {
            Authority::Registry { version, .. } => write!(f, "{version}"),
            Authority::Git { repository_url, target, .. } => {
                write!(f, "{repository_url}:{target}")
            },
            Authority::Path { path, .. } => write!(f, "{}", path.display()),
        }
    }
}