1#[derive(Default, Clone, Debug, PartialEq, Eq)]
5#[cfg_attr(feature = "clap", derive(clap::Args))]
6#[cfg_attr(feature = "clap", command(about = None, long_about = None))]
7#[non_exhaustive]
8pub struct Workspace {
9 #[cfg_attr(feature = "clap", arg(short, long, value_name = "SPEC"))]
10 pub package: Vec<String>,
12 #[cfg_attr(feature = "clap", arg(long))]
13 pub workspace: bool,
15 #[cfg_attr(
16 feature = "clap",
17 arg(long, hide_short_help(true), hide_long_help(true))
18 )]
19 pub all: bool,
21 #[cfg_attr(feature = "clap", arg(long, value_name = "SPEC"))]
22 pub exclude: Vec<String>,
24}
25
26#[cfg(feature = "cargo_metadata")]
27impl Workspace {
28 pub fn partition_packages<'m>(
34 &self,
35 meta: &'m cargo_metadata::Metadata,
36 ) -> (
37 Vec<&'m cargo_metadata::Package>,
38 Vec<&'m cargo_metadata::Package>,
39 ) {
40 let selection =
41 Packages::from_flags(self.workspace || self.all, &self.exclude, &self.package);
42 let workspace_members: std::collections::HashSet<_> =
43 meta.workspace_members.iter().collect();
44 let workspace_default_members: std::collections::HashSet<_> =
45 meta.workspace_default_members.iter().collect();
46 let base_ids: std::collections::HashSet<_> = match selection {
47 Packages::Default => workspace_default_members,
48 Packages::All => workspace_members,
49 Packages::OptOut(_) => workspace_members, Packages::Packages(patterns) => {
51 meta.packages
52 .iter()
53 .filter(|p| workspace_members.contains(&p.id) && patterns.contains(&p.name))
56 .map(|p| &p.id)
57 .collect()
58 }
59 };
60
61 meta.packages
62 .iter()
63 .partition(|p| base_ids.contains(&p.id) && !self.exclude.contains(&p.name))
65 }
66}
67
68#[derive(Clone, PartialEq, Eq, Debug)]
70#[cfg(feature = "cargo_metadata")]
71#[allow(clippy::enum_variant_names)]
72enum Packages<'p> {
73 Default,
74 All,
75 OptOut(&'p [String]),
76 Packages(&'p [String]),
77}
78
79#[cfg(feature = "cargo_metadata")]
80impl<'p> Packages<'p> {
81 fn from_flags(all: bool, exclude: &'p [String], package: &'p [String]) -> Self {
82 match (all, exclude.len(), package.len()) {
83 (false, 0, 0) => Packages::Default,
84 (false, 0, _) => Packages::Packages(package),
85 (false, _, 0) => Packages::OptOut(exclude), (false, _, _) => Packages::Packages(package), (true, 0, _) => Packages::All,
88 (true, _, _) => Packages::OptOut(exclude),
89 }
90 }
91}
92
93#[cfg(test)]
94mod test {
95 use super::*;
96
97 #[test]
98 #[cfg(feature = "clap")]
99 fn verify_app() {
100 #[derive(Debug, clap::Parser)]
101 struct Cli {
102 #[command(flatten)]
103 workspace: Workspace,
104 }
105
106 use clap::CommandFactory;
107 Cli::command().debug_assert();
108 }
109
110 #[test]
111 #[cfg(feature = "clap")]
112 fn parse_multiple_occurrences() {
113 use clap::Parser;
114
115 #[derive(PartialEq, Eq, Debug, Parser)]
116 struct Args {
117 positional: Option<String>,
118 #[command(flatten)]
119 workspace: Workspace,
120 }
121
122 assert_eq!(
123 Args {
124 positional: None,
125 workspace: Workspace {
126 package: vec![],
127 workspace: false,
128 all: false,
129 exclude: vec![],
130 }
131 },
132 Args::parse_from(["test"])
133 );
134 assert_eq!(
135 Args {
136 positional: Some("baz".to_owned()),
137 workspace: Workspace {
138 package: vec!["foo".to_owned(), "bar".to_owned()],
139 workspace: false,
140 all: false,
141 exclude: vec![],
142 }
143 },
144 Args::parse_from(["test", "--package", "foo", "--package", "bar", "baz"])
145 );
146 assert_eq!(
147 Args {
148 positional: Some("baz".to_owned()),
149 workspace: Workspace {
150 package: vec![],
151 workspace: false,
152 all: false,
153 exclude: vec!["foo".to_owned(), "bar".to_owned()],
154 }
155 },
156 Args::parse_from(["test", "--exclude", "foo", "--exclude", "bar", "baz"])
157 );
158 }
159
160 #[cfg(feature = "cargo_metadata")]
161 #[cfg(test)]
162 mod partition_default {
163 use super::*;
164
165 #[test]
166 fn single_crate() {
167 let mut metadata = cargo_metadata::MetadataCommand::new();
168 metadata.manifest_path("tests/fixtures/simple/Cargo.toml");
169 let metadata = metadata.exec().unwrap();
170
171 let workspace = Workspace {
172 ..Default::default()
173 };
174 let (included, excluded) = workspace.partition_packages(&metadata);
175 assert_eq!(included.len(), 1);
176 assert_eq!(excluded.len(), 0);
177 }
178
179 #[test]
180 fn mixed_ws_root() {
181 let mut metadata = cargo_metadata::MetadataCommand::new();
182 metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml");
183 let metadata = metadata.exec().unwrap();
184
185 let workspace = Workspace {
186 ..Default::default()
187 };
188 let (included, excluded) = workspace.partition_packages(&metadata);
189 assert_eq!(included.len(), 1);
190 assert_eq!(excluded.len(), 2);
191 }
192
193 #[test]
194 fn mixed_ws_leaf() {
195 let mut metadata = cargo_metadata::MetadataCommand::new();
196 metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml");
197 let metadata = metadata.exec().unwrap();
198
199 let workspace = Workspace {
200 ..Default::default()
201 };
202 let (included, excluded) = workspace.partition_packages(&metadata);
203 assert_eq!(included.len(), 1);
204 assert_eq!(excluded.len(), 2);
205 }
206
207 #[test]
208 fn pure_ws_root() {
209 let mut metadata = cargo_metadata::MetadataCommand::new();
210 metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml");
211 let metadata = metadata.exec().unwrap();
212
213 let workspace = Workspace {
214 ..Default::default()
215 };
216 let (included, excluded) = workspace.partition_packages(&metadata);
217 assert_eq!(included.len(), 3);
218 assert_eq!(excluded.len(), 0);
219 }
220
221 #[test]
222 fn pure_ws_leaf() {
223 let mut metadata = cargo_metadata::MetadataCommand::new();
224 metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml");
225 let metadata = metadata.exec().unwrap();
226
227 let workspace = Workspace {
228 ..Default::default()
229 };
230 let (included, excluded) = workspace.partition_packages(&metadata);
231 assert_eq!(included.len(), 1);
232 assert_eq!(excluded.len(), 2);
233 }
234 }
235
236 #[cfg(feature = "cargo_metadata")]
237 #[cfg(test)]
238 mod partition_all {
239 use super::*;
240
241 #[test]
242 fn single_crate() {
243 let mut metadata = cargo_metadata::MetadataCommand::new();
244 metadata.manifest_path("tests/fixtures/simple/Cargo.toml");
245 let metadata = metadata.exec().unwrap();
246
247 let workspace = Workspace {
248 all: true,
249 ..Default::default()
250 };
251 let (included, excluded) = workspace.partition_packages(&metadata);
252 assert_eq!(included.len(), 1);
253 assert_eq!(excluded.len(), 0);
254 }
255
256 #[test]
257 fn mixed_ws_root() {
258 let mut metadata = cargo_metadata::MetadataCommand::new();
259 metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml");
260 let metadata = metadata.exec().unwrap();
261
262 let workspace = Workspace {
263 all: true,
264 ..Default::default()
265 };
266 let (included, excluded) = workspace.partition_packages(&metadata);
267 assert_eq!(included.len(), 3);
268 assert_eq!(excluded.len(), 0);
269 }
270
271 #[test]
272 fn mixed_ws_leaf() {
273 let mut metadata = cargo_metadata::MetadataCommand::new();
274 metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml");
275 let metadata = metadata.exec().unwrap();
276
277 let workspace = Workspace {
278 all: true,
279 ..Default::default()
280 };
281 let (included, excluded) = workspace.partition_packages(&metadata);
282 assert_eq!(included.len(), 3);
283 assert_eq!(excluded.len(), 0);
284 }
285
286 #[test]
287 fn pure_ws_root() {
288 let mut metadata = cargo_metadata::MetadataCommand::new();
289 metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml");
290 let metadata = metadata.exec().unwrap();
291
292 let workspace = Workspace {
293 all: true,
294 ..Default::default()
295 };
296 let (included, excluded) = workspace.partition_packages(&metadata);
297 assert_eq!(included.len(), 3);
298 assert_eq!(excluded.len(), 0);
299 }
300
301 #[test]
302 fn pure_ws_leaf() {
303 let mut metadata = cargo_metadata::MetadataCommand::new();
304 metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml");
305 let metadata = metadata.exec().unwrap();
306
307 let workspace = Workspace {
308 all: true,
309 ..Default::default()
310 };
311 let (included, excluded) = workspace.partition_packages(&metadata);
312 assert_eq!(included.len(), 3);
313 assert_eq!(excluded.len(), 0);
314 }
315 }
316
317 #[cfg(feature = "cargo_metadata")]
318 #[cfg(test)]
319 mod partition_package {
320 use super::*;
321
322 #[test]
323 fn single_crate() {
324 let mut metadata = cargo_metadata::MetadataCommand::new();
325 metadata.manifest_path("tests/fixtures/simple/Cargo.toml");
326 let metadata = metadata.exec().unwrap();
327
328 let workspace = Workspace {
329 package: vec!["simple".to_owned()],
330 ..Default::default()
331 };
332 let (included, excluded) = workspace.partition_packages(&metadata);
333 assert_eq!(included.len(), 1);
334 assert_eq!(excluded.len(), 0);
335 }
336
337 #[test]
338 fn mixed_ws_root() {
339 let mut metadata = cargo_metadata::MetadataCommand::new();
340 metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml");
341 let metadata = metadata.exec().unwrap();
342
343 let workspace = Workspace {
344 package: vec!["a".to_owned()],
345 ..Default::default()
346 };
347 let (included, excluded) = workspace.partition_packages(&metadata);
348 assert_eq!(included.len(), 1);
349 assert_eq!(excluded.len(), 2);
350 }
351
352 #[test]
353 fn mixed_ws_leaf() {
354 let mut metadata = cargo_metadata::MetadataCommand::new();
355 metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml");
356 let metadata = metadata.exec().unwrap();
357
358 let workspace = Workspace {
359 package: vec!["a".to_owned()],
360 ..Default::default()
361 };
362 let (included, excluded) = workspace.partition_packages(&metadata);
363 assert_eq!(included.len(), 1);
364 assert_eq!(excluded.len(), 2);
365 }
366
367 #[test]
368 fn pure_ws_root() {
369 let mut metadata = cargo_metadata::MetadataCommand::new();
370 metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml");
371 let metadata = metadata.exec().unwrap();
372
373 let workspace = Workspace {
374 package: vec!["a".to_owned()],
375 ..Default::default()
376 };
377 let (included, excluded) = workspace.partition_packages(&metadata);
378 assert_eq!(included.len(), 1);
379 assert_eq!(excluded.len(), 2);
380 }
381
382 #[test]
383 fn pure_ws_leaf() {
384 let mut metadata = cargo_metadata::MetadataCommand::new();
385 metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml");
386 let metadata = metadata.exec().unwrap();
387
388 let workspace = Workspace {
389 package: vec!["a".to_owned()],
390 ..Default::default()
391 };
392 let (included, excluded) = workspace.partition_packages(&metadata);
393 assert_eq!(included.len(), 1);
394 assert_eq!(excluded.len(), 2);
395 }
396 }
397}