1use std::path::{Path, PathBuf};
8
9use super::{PkgError, PkgResult};
10
11pub struct Store {
13 root: PathBuf,
14}
15
16impl Store {
17 pub fn user_default() -> PkgResult<Store> {
20 if let Ok(custom) = std::env::var("STRYKE_HOME") {
21 return Ok(Store {
22 root: PathBuf::from(custom),
23 });
24 }
25 let home = std::env::var("HOME")
26 .map_err(|_| PkgError::Other("HOME environment variable not set".into()))?;
27 Ok(Store {
28 root: PathBuf::from(home).join(".stryke"),
29 })
30 }
31
32 pub fn at(root: impl Into<PathBuf>) -> Store {
34 Store { root: root.into() }
35 }
36
37 pub fn root(&self) -> &Path {
38 &self.root
39 }
40 pub fn store_dir(&self) -> PathBuf {
41 self.root.join("store")
42 }
43 pub fn cache_dir(&self) -> PathBuf {
44 self.root.join("cache")
45 }
46 pub fn git_dir(&self) -> PathBuf {
47 self.root.join("git")
48 }
49 pub fn bin_dir(&self) -> PathBuf {
50 self.root.join("bin")
51 }
52 pub fn index_dir(&self) -> PathBuf {
53 self.root.join("index")
54 }
55
56 pub fn package_dir(&self, name: &str, version: &str) -> PathBuf {
58 self.store_dir().join(format!("{}@{}", name, version))
59 }
60
61 pub fn ensure_layout(&self) -> PkgResult<()> {
64 for d in [
65 self.store_dir(),
66 self.cache_dir(),
67 self.git_dir(),
68 self.bin_dir(),
69 self.index_dir(),
70 ] {
71 std::fs::create_dir_all(&d)
72 .map_err(|e| PkgError::Io(format!("create {}: {}", d.display(), e)))?;
73 }
74 Ok(())
75 }
76
77 pub fn has_package(&self, name: &str, version: &str) -> bool {
79 self.package_dir(name, version).is_dir()
80 }
81
82 pub fn install_path_dep(&self, name: &str, version: &str, src: &Path) -> PkgResult<PathBuf> {
86 let dst = self.package_dir(name, version);
87 if dst.exists() {
88 std::fs::remove_dir_all(&dst)
89 .map_err(|e| PkgError::Io(format!("clear {}: {}", dst.display(), e)))?;
90 }
91 std::fs::create_dir_all(&dst)?;
92 copy_dir(src, &dst)?;
93 Ok(dst)
94 }
95}
96
97fn copy_dir(src: &Path, dst: &Path) -> PkgResult<()> {
100 for entry in std::fs::read_dir(src)? {
101 let entry = entry?;
102 let from = entry.path();
103 let name = entry.file_name();
104 let to = dst.join(&name);
105 let meta = entry.metadata()?;
106 if meta.file_type().is_symlink() {
107 #[cfg(unix)]
108 {
109 let target = std::fs::read_link(&from)?;
110 std::os::unix::fs::symlink(target, &to)
111 .map_err(|e| PkgError::Io(format!("symlink {}: {}", to.display(), e)))?;
112 }
113 #[cfg(not(unix))]
114 std::fs::copy(&from, &to)
115 .map_err(|e| PkgError::Io(format!("copy {}: {}", from.display(), e)))?;
116 } else if meta.is_dir() {
117 std::fs::create_dir_all(&to)?;
118 copy_dir(&from, &to)?;
119 } else {
120 std::fs::copy(&from, &to)
121 .map_err(|e| PkgError::Io(format!("copy {}: {}", from.display(), e)))?;
122 }
123 }
124 Ok(())
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 fn tempdir() -> PathBuf {
132 let pid = std::process::id();
133 let nanos = std::time::SystemTime::now()
134 .duration_since(std::time::UNIX_EPOCH)
135 .unwrap()
136 .subsec_nanos();
137 let p = std::env::temp_dir().join(format!("stryke-store-test-{}-{}", pid, nanos));
138 let _ = std::fs::remove_dir_all(&p);
139 std::fs::create_dir_all(&p).unwrap();
140 p
141 }
142
143 #[test]
144 fn ensure_layout_creates_subdirs() {
145 let root = tempdir();
146 let s = Store::at(&root);
147 s.ensure_layout().unwrap();
148 assert!(s.store_dir().is_dir());
149 assert!(s.cache_dir().is_dir());
150 assert!(s.git_dir().is_dir());
151 assert!(s.bin_dir().is_dir());
152 assert!(s.index_dir().is_dir());
153 }
154
155 #[test]
156 fn package_dir_path_shape() {
157 let s = Store::at("/x");
158 assert_eq!(
159 s.package_dir("http", "1.0.0"),
160 PathBuf::from("/x/store/http@1.0.0")
161 );
162 }
163
164 #[test]
165 fn install_path_dep_round_trip() {
166 let store_root = tempdir();
167 let src = tempdir();
168 std::fs::create_dir_all(src.join("lib")).unwrap();
169 std::fs::write(src.join("lib/Foo.stk"), b"sub foo { 1 }").unwrap();
170 std::fs::write(
171 src.join("stryke.toml"),
172 "[package]\nname = \"foo\"\nversion = \"0.1.0\"\n",
173 )
174 .unwrap();
175 let s = Store::at(&store_root);
176 s.ensure_layout().unwrap();
177 let dst = s.install_path_dep("foo", "0.1.0", &src).unwrap();
178 assert!(dst.is_dir());
179 assert!(dst.join("lib/Foo.stk").is_file());
180 assert!(dst.join("stryke.toml").is_file());
181 }
182}