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