1use super::{BloatDir, EnforcePolicy, PackageManager, dir_size, enforce_two_tier, run_command};
7use anyhow::Result;
8use std::fs;
9use std::path::Path;
10
11pub struct Uv;
13
14impl PackageManager for Uv {
15 fn name(&self) -> &'static str {
16 "uv"
17 }
18
19 fn detect(&self, path: &Path) -> bool {
20 let uv_lock = path.join("uv.lock");
21 if uv_lock.exists() {
22 return true;
23 }
24
25 let pyproject = path.join("pyproject.toml");
26 if pyproject.exists() {
27 if let Ok(content) = fs::read_to_string(&pyproject) {
28 if content.contains("[tool.uv]") {
29 return true;
30 }
31 }
32 }
33
34 false
35 }
36
37 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
38 let mut dirs = Vec::new();
39 let venv_path = path.join(".venv");
40 if venv_path.exists() {
41 dirs.push(BloatDir {
42 name: ".venv".to_string(),
43 path: venv_path.clone(),
44 size_bytes: dir_size(&venv_path),
45 });
46 }
47 dirs
48 }
49
50 fn enforce_lockfile(&self, path: &Path, policy: EnforcePolicy) -> Result<()> {
56 let lockfile = path.join("uv.lock");
57 enforce_two_tier(
58 &lockfile,
59 "uv",
60 &["lock", "--locked"],
61 &["lock"],
62 path,
63 policy,
64 )
65 }
66
67 fn restore(&self, path: &Path) -> Result<()> {
68 run_command("uv", &["sync"], path)
69 }
70
71 fn lockfiles(&self) -> &'static [&'static str] {
72 &["uv.lock"]
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use std::fs::File;
80 use std::io::Write;
81 use tempfile::tempdir;
82
83 #[test]
84 fn test_name() {
85 let adapter = Uv;
86 assert_eq!(adapter.name(), "uv");
87 }
88
89 #[test]
90 fn test_detect_positive_lock() {
91 let dir = tempdir().unwrap();
92 File::create(dir.path().join("uv.lock")).unwrap();
93
94 let adapter = Uv;
95 assert!(adapter.detect(dir.path()));
96 }
97
98 #[test]
99 fn test_detect_positive_toml() {
100 let dir = tempdir().unwrap();
101 let mut file = File::create(dir.path().join("pyproject.toml")).unwrap();
102 writeln!(file, "[tool.uv]").unwrap();
103
104 let adapter = Uv;
105 assert!(adapter.detect(dir.path()));
106 }
107
108 #[test]
109 fn test_detect_negative() {
110 let dir = tempdir().unwrap();
111
112 let adapter = Uv;
113 assert!(!adapter.detect(dir.path()));
114 }
115
116 #[test]
117 fn test_bloat_dirs_present() {
118 let dir = tempdir().unwrap();
119 fs::create_dir(dir.path().join(".venv")).unwrap();
120
121 let adapter = Uv;
122 let dirs = adapter.bloat_dirs(dir.path());
123 assert_eq!(dirs.len(), 1);
124 assert_eq!(dirs[0].name, ".venv");
125 }
126
127 #[test]
128 fn test_bloat_dirs_absent() {
129 let dir = tempdir().unwrap();
130
131 let adapter = Uv;
132 let dirs = adapter.bloat_dirs(dir.path());
133 assert!(dirs.is_empty());
134 }
135}