neyuki 0.6.1

A small Lua-like scripting language implemented in Rust
//! `neyuki.lock` with SHA-256 checksums.

use std::str::FromStr;

use super::version::Version;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LockedPackage {
    pub name: String,
    pub version: Version,
    pub checksum: String,
    pub dependencies: Vec<String>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Lockfile {
    pub packages: Vec<LockedPackage>,
}

impl Lockfile {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add_package(&mut self, pkg: LockedPackage) {
        if let Some(pos) = self.packages.iter().position(|p| p.name == pkg.name) {
            self.packages[pos] = pkg;
        } else {
            self.packages.push(pkg);
        }
    }

    pub fn get_package(&self, name: &str) -> Option<&LockedPackage> {
        self.packages.iter().find(|p| p.name == name)
    }

    pub fn parse(content: &str) -> Result<Self, String> {
        let mut lockfile = Self::new();
        let mut cur_name = String::new();
        let mut cur_ver = None;
        let mut cur_checksum = String::new();
        let mut cur_deps = Vec::new();
        let mut in_pkg = false;

        for line in content.lines() {
            let trimmed = line.trim();
            if trimmed.is_empty() || trimmed.starts_with('#') {
                continue;
            }

            if trimmed == "[[package]]" {
                if let (true, Some(v)) = (in_pkg && !cur_name.is_empty(), cur_ver.take()) {
                    lockfile.add_package(LockedPackage {
                        name: cur_name.clone(),
                        version: v,
                        checksum: cur_checksum.clone(),
                        dependencies: std::mem::take(&mut cur_deps),
                    });
                }
                in_pkg = true;
                cur_name.clear();
                cur_ver = None;
                cur_checksum.clear();
                cur_deps.clear();
                continue;
            }

            if !in_pkg {
                continue;
            }

            let Some((k, v_raw)) = trimmed.split_once('=') else {
                continue;
            };
            let k = k.trim();
            let v = v_raw.trim().trim_matches('"').trim_matches('\'');

            match k {
                "name" => cur_name = v.to_string(),
                "version" => cur_ver = Some(Version::from_str(v)?),
                "checksum" => cur_checksum = v.to_string(),
                "dependencies" => {
                    // e.g. ["dep1", "dep2"]
                    let inner = v.trim_matches('[').trim_matches(']');
                    cur_deps = inner
                        .split(',')
                        .map(|s| s.trim().trim_matches('"').trim_matches('\'').to_string())
                        .filter(|s| !s.is_empty())
                        .collect();
                }
                _ => {}
            }
        }

        if let (true, Some(v)) = (in_pkg && !cur_name.is_empty(), cur_ver) {
            lockfile.add_package(LockedPackage {
                name: cur_name,
                version: v,
                checksum: cur_checksum,
                dependencies: cur_deps,
            });
        }

        Ok(lockfile)
    }

    pub fn serialize(&self) -> String {
        let mut out = String::new();
        out.push_str("# This file is automatically generated by Neyuki Package Manager.\n");
        out.push_str("# Manual edits may be overwritten.\n\n");

        for pkg in &self.packages {
            out.push_str("[[package]]\n");
            out.push_str(&format!("name = \"{}\"\n", pkg.name));
            out.push_str(&format!("version = \"{}\"\n", pkg.version));
            out.push_str(&format!("checksum = \"{}\"\n", pkg.checksum));
            let deps_quoted: Vec<String> = pkg
                .dependencies
                .iter()
                .map(|d| format!("\"{}\"", d))
                .collect();
            out.push_str(&format!("dependencies = [{}]\n\n", deps_quoted.join(", ")));
        }

        out
    }
}