use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct PackagesConfig {
pub npm: Option<NpmConfig>,
pub pip: Option<PipConfig>,
pub cargo: Option<CargoConfig>,
pub gem: Option<GemConfig>,
pub go: Option<GoConfig>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum PackageSpec {
Version(String),
Detailed {
version: String,
#[serde(default)]
optional: bool,
#[serde(default)]
features: Vec<String>,
},
}
impl PackageSpec {
pub fn version(&self) -> &str {
match self {
PackageSpec::Version(v) => v,
PackageSpec::Detailed { version, .. } => version,
}
}
pub fn is_optional(&self) -> bool {
match self {
PackageSpec::Version(_) => false,
PackageSpec::Detailed { optional, .. } => *optional,
}
}
pub fn features(&self) -> &[String] {
match self {
PackageSpec::Version(_) => &[],
PackageSpec::Detailed { features, .. } => features,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct NpmConfig {
#[serde(flatten)]
pub packages: HashMap<String, PackageSpec>,
#[serde(default)]
pub package_manager: Option<NpmPackageManager>,
#[serde(default)]
pub from_lockfile: bool,
#[serde(default = "default_true")]
pub install_dev: bool,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum NpmPackageManager {
#[default]
Npm,
Yarn,
Pnpm,
}
impl NpmPackageManager {
pub fn command(&self) -> &'static str {
match self {
NpmPackageManager::Npm => "npm",
NpmPackageManager::Yarn => "yarn",
NpmPackageManager::Pnpm => "pnpm",
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct PipConfig {
#[serde(flatten)]
pub packages: HashMap<String, PackageSpec>,
#[serde(default)]
pub venv: Option<String>,
#[serde(default = "default_true")]
pub create_venv: bool,
#[serde(default)]
pub from_lockfile: bool,
#[serde(default)]
pub lockfile: Option<String>,
#[serde(default = "default_true")]
pub activate_hint: bool,
#[serde(default)]
pub system_site_packages: bool,
#[serde(default)]
pub python_version: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct CargoConfig {
#[serde(flatten)]
pub packages: HashMap<String, PackageSpec>,
#[serde(default)]
pub locked: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct GemConfig {
#[serde(flatten)]
pub packages: HashMap<String, PackageSpec>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct GoConfig {
#[serde(flatten)]
pub packages: HashMap<String, PackageSpec>,
}
fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_npm_config() {
let toml_str = r#"
typescript = "^5.0"
eslint = "latest"
"#;
let config: NpmConfig = toml::from_str(toml_str).unwrap();
assert_eq!(config.packages.len(), 2);
assert!(matches!(
config.packages.get("typescript"),
Some(PackageSpec::Version(v)) if v == "^5.0"
));
}
#[test]
fn test_parse_npm_with_package_manager() {
let toml_str = r#"
typescript = "^5.0"
package_manager = "pnpm"
"#;
let config: NpmConfig = toml::from_str(toml_str).unwrap();
assert_eq!(config.package_manager, Some(NpmPackageManager::Pnpm));
}
#[test]
fn test_parse_pip_with_venv() {
let toml_str = r#"
pytest = ">=7.0"
venv = ".venv"
create_venv = true
"#;
let config: PipConfig = toml::from_str(toml_str).unwrap();
assert_eq!(config.venv, Some(".venv".to_string()));
assert!(config.create_venv);
assert!(config.packages.contains_key("pytest"));
}
#[test]
fn test_parse_cargo_config() {
let toml_str = r#"
cargo-watch = "latest"
cargo-nextest = "latest"
locked = true
"#;
let config: CargoConfig = toml::from_str(toml_str).unwrap();
assert!(config.locked);
assert_eq!(config.packages.len(), 2);
}
#[test]
fn test_package_spec_detailed() {
let toml_str = r#"
[some-crate]
version = "1.0.0"
optional = true
features = ["feature1", "feature2"]
"#;
#[derive(Deserialize)]
struct Test {
#[serde(rename = "some-crate")]
some_crate: PackageSpec,
}
let test: Test = toml::from_str(toml_str).unwrap();
assert!(test.some_crate.is_optional());
assert_eq!(test.some_crate.features().len(), 2);
}
}