wally 0.1.0

Package manager for Roblox
Documentation
use std::path::Path;
use std::{
    fs::read_to_string,
    io::{self, BufWriter, Write},
};

use fs_err::File;
use semver::Version;
use serde::{Deserialize, Serialize};

use crate::{
    manifest::Manifest, package_id::PackageId, package_name::PackageName, resolution::Resolve,
};

pub const LOCKFILE_NAME: &str = "wally.lock";

#[derive(Debug, Serialize, Deserialize)]
pub struct Lockfile {
    pub registry: String,

    #[serde(rename = "package")]
    pub packages: Vec<LockPackage>,
}

impl Lockfile {
    pub fn from_manifest(manifest: &Manifest) -> Self {
        Self {
            registry: manifest.package.registry.clone(),
            packages: Vec::new(),
        }
    }

    pub fn from_resolve(resolve: &Resolve) -> Self {
        let mut packages = Vec::new();

        for package_id in &resolve.activated {
            let dependencies = resolve
                .shared_dependencies
                .get(package_id)
                .map(|dependencies| {
                    dependencies
                        .iter()
                        .map(|(key, value)| (key.clone(), value.clone()))
                        .collect()
                })
                .unwrap_or_else(Vec::new);

            packages.push(LockPackage::Registry(RegistryLockPackage {
                name: package_id.name().clone(),
                version: package_id.version().clone(),
                checksum: None,
                dependencies,
            }));
        }

        Self {
            registry: "test".to_owned(),
            packages,
        }
    }

    pub fn load(project_path: &Path) -> anyhow::Result<Option<Self>> {
        let lockfile_path = project_path.join(LOCKFILE_NAME);
        let contents = match read_to_string(&lockfile_path) {
            Ok(contents) => contents,
            Err(err) => {
                if err.kind() == io::ErrorKind::NotFound {
                    return Ok(None);
                } else {
                    return Err(err.into());
                }
            }
        };
        Ok(Some(toml::from_str(&contents)?))
    }

    pub fn save(&self, project_path: &Path) -> anyhow::Result<()> {
        let lockfile_path = project_path.join(LOCKFILE_NAME);
        let serialized = toml::to_string(self)?;

        let mut file = BufWriter::new(File::create(lockfile_path)?);
        writeln!(file, "# This file is automatically @generated by Wally.")?;
        writeln!(file, "# It is not intended for manual editing.")?;
        write!(file, "{}", serialized)?;
        file.flush()?;

        Ok(())
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LockPackage {
    Registry(RegistryLockPackage),
    Git(GitLockPackage),
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RegistryLockPackage {
    pub name: PackageName,
    pub version: Version,
    pub checksum: Option<String>,

    #[serde(default)]
    pub dependencies: Vec<(String, PackageId)>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GitLockPackage {
    pub name: String,
    pub rev: String,
    pub commit: String,

    #[serde(default)]
    pub dependencies: Vec<PackageId>,
}