1use tempfile::TempDir;
2
3enum ProjectKind {
4 Empty,
5 Cargo,
6}
7
8pub struct TestProject {
14 kind: ProjectKind,
15 dependencies: Vec<(String, String)>,
16 dev_dependencies: Vec<(String, String)>,
17 members: Vec<(String, TestMember)>,
18 source_files: Vec<(String, String)>,
19 scute_config: Option<String>,
20}
21
22pub struct TestMember {
24 dependencies: Vec<(String, String)>,
25 dev_dependencies: Vec<(String, String)>,
26}
27
28impl TestMember {
29 pub fn dependency(mut self, name: &str, version: &str) -> Self {
30 self.dependencies.push((name.into(), version.into()));
31 self
32 }
33
34 pub fn dev_dependency(mut self, name: &str, version: &str) -> Self {
35 self.dev_dependencies.push((name.into(), version.into()));
36 self
37 }
38}
39
40impl TestProject {
41 pub fn empty() -> Self {
43 Self {
44 kind: ProjectKind::Empty,
45 dependencies: Vec::new(),
46 dev_dependencies: Vec::new(),
47 members: Vec::new(),
48 source_files: Vec::new(),
49 scute_config: None,
50 }
51 }
52
53 pub fn cargo() -> Self {
55 Self {
56 kind: ProjectKind::Cargo,
57 dependencies: Vec::new(),
58 dev_dependencies: Vec::new(),
59 members: Vec::new(),
60 source_files: Vec::new(),
61 scute_config: None,
62 }
63 }
64
65 pub fn dependency(mut self, name: &str, version: &str) -> Self {
66 self.dependencies.push((name.into(), version.into()));
67 self
68 }
69
70 pub fn dev_dependency(mut self, name: &str, version: &str) -> Self {
71 self.dev_dependencies.push((name.into(), version.into()));
72 self
73 }
74
75 pub fn member(mut self, name: &str, build: impl FnOnce(TestMember) -> TestMember) -> Self {
78 let member = build(TestMember {
79 dependencies: Vec::new(),
80 dev_dependencies: Vec::new(),
81 });
82 self.members.push((name.into(), member));
83 self
84 }
85
86 pub fn source_file(mut self, name: &str, content: &str) -> Self {
87 self.source_files.push((name.into(), content.into()));
88 self
89 }
90
91 pub fn scute_config(mut self, yaml: &str) -> Self {
92 self.scute_config = Some(yaml.into());
93 self
94 }
95
96 pub fn build(self) -> TempDir {
98 let dir = TempDir::new().unwrap();
99 if matches!(self.kind, ProjectKind::Cargo) {
100 setup_cargo_project(
101 &dir,
102 &self.dependencies,
103 &self.dev_dependencies,
104 &self.members,
105 );
106 }
107 for (name, content) in &self.source_files {
108 let path = dir.path().join(name);
109 if let Some(parent) = path.parent() {
110 std::fs::create_dir_all(parent).unwrap();
111 }
112 std::fs::write(path, content).unwrap();
113 }
114 if let Some(yaml) = &self.scute_config {
115 std::fs::write(dir.path().join(".scute.yml"), yaml).unwrap();
116 }
117 dir
118 }
119}
120
121fn setup_cargo_project(
122 dir: &TempDir,
123 dependencies: &[(String, String)],
124 dev_dependencies: &[(String, String)],
125 members: &[(String, TestMember)],
126) {
127 let mut toml = if members.is_empty() {
128 String::from(
129 "[package]\nname = \"test-project\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
130 )
131 } else {
132 let names: Vec<&str> = members.iter().map(|(n, _)| n.as_str()).collect();
133 format!(
134 "[workspace]\nmembers = [{}]\n",
135 names
136 .iter()
137 .map(|n| format!("\"{n}\""))
138 .collect::<Vec<_>>()
139 .join(", ")
140 )
141 };
142
143 append_cargo_deps(&mut toml, dependencies, dev_dependencies);
144
145 std::fs::write(dir.path().join("Cargo.toml"), toml).unwrap();
146
147 if members.is_empty() {
148 let src = dir.path().join("src");
149 std::fs::create_dir(&src).unwrap();
150 std::fs::write(src.join("lib.rs"), "").unwrap();
151 }
152
153 for (name, member) in members {
154 setup_cargo_member(dir, name, &member.dependencies, &member.dev_dependencies);
155 }
156}
157
158fn setup_cargo_member(
159 dir: &TempDir,
160 name: &str,
161 dependencies: &[(String, String)],
162 dev_dependencies: &[(String, String)],
163) {
164 let member_dir = dir.path().join(name);
165 std::fs::create_dir_all(member_dir.join("src")).unwrap();
166 std::fs::write(member_dir.join("src/lib.rs"), "").unwrap();
167
168 let mut toml =
169 format!("[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2021\"\n");
170 append_cargo_deps(&mut toml, dependencies, dev_dependencies);
171 std::fs::write(member_dir.join("Cargo.toml"), toml).unwrap();
172}
173
174fn append_cargo_deps(
175 toml: &mut String,
176 dependencies: &[(String, String)],
177 dev_dependencies: &[(String, String)],
178) {
179 use std::fmt::Write;
180
181 for (section, deps) in [
182 ("[dependencies]", dependencies),
183 ("[dev-dependencies]", dev_dependencies),
184 ] {
185 if !deps.is_empty() {
186 writeln!(toml, "\n{section}").unwrap();
187 for (name, version) in deps {
188 writeln!(toml, "{name} = \"{version}\"").unwrap();
189 }
190 }
191 }
192}