gipm 0.1.0

Gipm is a git package manager
Documentation
use anyhow::Context;
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};
use std::{
    collections::BTreeMap,
    fs::File,
    io::{Read, Write},
    path::Path,
};

#[derive(Serialize, Deserialize)]
pub struct LockFile {
    pub version: String,
    pub packages: BTreeMap<String, LockFilePackage>,
}

#[derive(Serialize, Deserialize)]
pub struct LockFilePackage {
    pub version: Version,
    pub source: String,
    pub commit: String,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub dependencies: BTreeMap<String, VersionReq>,
}

impl Default for LockFile {
    fn default() -> Self {
        Self::new()
    }
}

impl LockFile {
    pub fn new() -> Self {
        Self {
            version: "1".to_string(),
            packages: BTreeMap::new(),
        }
    }

    pub fn from_file<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
        let mut file = File::open(path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        let lockfile: LockFile =
            toml::from_str(&contents).context("Failed to parse lock file contents")?;
        Ok(lockfile)
    }

    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
        let mut file = File::create(path)?;
        let contents = toml::to_string_pretty(&self)?;
        file.write_all(contents.as_bytes())?;
        Ok(())
    }

    pub fn add_package(
        &mut self,
        name: String,
        version: Version,
        source: String,
        commit: String,
        dependencies: Option<BTreeMap<String, VersionReq>>,
    ) -> anyhow::Result<()> {
        self.packages.insert(
            name,
            LockFilePackage {
                version,
                source,
                commit,
                dependencies: dependencies.unwrap_or_default(),
            },
        );
        Ok(())
    }
}