1
+ use std:: collections:: HashSet ;
1
2
use std:: fmt;
2
3
3
4
use anyhow:: { bail, ensure, Result } ;
@@ -22,8 +23,15 @@ use scarb_metadata::{Metadata, PackageMetadata};
22
23
pub struct PackagesFilter {
23
24
/// Packages to run this command on, can be a concrete package name (`foobar`) or
24
25
/// a prefix glob (`foo*`).
25
- #[ arg( short, long, value_name = "SPEC" , default_value = "*" ) ]
26
- package : String ,
26
+ #[ arg(
27
+ short,
28
+ long,
29
+ default_value = "*" ,
30
+ value_delimiter = ',' ,
31
+ value_name = "SPEC" ,
32
+ env = "SCARB_PACKAGES_FILTER"
33
+ ) ]
34
+ package : Vec < String > ,
27
35
/// Run for all packages in the workspace.
28
36
#[ arg( short, long, conflicts_with = "package" ) ]
29
37
workspace : bool ,
@@ -34,31 +42,38 @@ impl PackagesFilter {
34
42
///
35
43
/// Returns an error if no or more than one packages were found.
36
44
pub fn match_one < S : PackagesSource > ( & self , source : & S ) -> Result < S :: Package > {
37
- let spec = Spec :: parse ( & self . package ) ?;
45
+ let specs = self . package_specs ( ) ?;
38
46
39
47
// Check for current package.
40
48
// If none (in case of virtual workspace), run for all members.
41
- if self . current_selected ( & spec ) {
49
+ if self . current_selected ( & specs ) {
42
50
if let Some ( pkg) = self . current_package ( source) ? {
43
51
return Ok ( pkg) ;
44
52
}
45
53
}
46
54
47
55
let members = source. members ( ) ;
48
56
49
- if ( self . workspace || matches ! ( spec, Spec :: All ) ) && members. len ( ) > 1 {
57
+ if ( self . workspace || specs. iter ( ) . any ( |spec| matches ! ( spec, Spec :: All ) ) )
58
+ && members. len ( ) > 1
59
+ {
50
60
bail ! ( indoc! { r#"
51
61
could not determine which package to work on
52
62
help: use the `--package` option to specify the package
53
63
"# } ) ;
54
64
}
55
65
56
- let found = Self :: do_match :: < S > ( & spec, self . workspace , members. into_iter ( ) ) ?;
66
+ let specs_filter: String = specs
67
+ . iter ( )
68
+ . map ( |s| s. to_string ( ) )
69
+ . collect :: < Vec < _ > > ( )
70
+ . join ( "," ) ;
71
+ let found = Self :: do_match_all :: < S > ( specs, self . workspace , members) ?;
57
72
58
73
ensure ! (
59
74
found. len( ) <= 1 ,
60
75
formatdoc! { r#"
61
- workspace has multiple members matching `{spec }`
76
+ workspace has multiple members matching `{specs_filter }`
62
77
help: use the `--package` option to specify single package
63
78
"# }
64
79
) ;
@@ -70,18 +85,31 @@ impl PackagesFilter {
70
85
///
71
86
/// Returns an error if no packages were found.
72
87
pub fn match_many < S : PackagesSource > ( & self , source : & S ) -> Result < Vec < S :: Package > > {
73
- let spec = Spec :: parse ( & self . package ) ?;
88
+ let specs = self . package_specs ( ) ?;
74
89
75
90
// Check for current package.
76
91
// If none (in case of virtual workspace), run for all members.
77
- if self . current_selected ( & spec ) {
92
+ if self . current_selected ( & specs ) {
78
93
if let Some ( pkg) = self . current_package ( source) ? {
79
94
return Ok ( vec ! [ pkg] ) ;
80
95
}
81
96
}
82
97
83
98
let members = source. members ( ) ;
84
- Self :: do_match :: < S > ( & spec, self . workspace , members. into_iter ( ) )
99
+ Self :: do_match_all :: < S > ( specs, self . workspace , members)
100
+ }
101
+
102
+ fn package_specs ( & self ) -> Result < Vec < Spec > > {
103
+ let specs = self
104
+ . package
105
+ . iter ( )
106
+ . map ( |s| Spec :: parse ( s) )
107
+ . collect :: < Result < HashSet < Spec > > > ( ) ?;
108
+ if specs. iter ( ) . any ( |s| matches ! ( s, Spec :: All ) ) {
109
+ Ok ( vec ! [ Spec :: All ] )
110
+ } else {
111
+ Ok ( specs. into_iter ( ) . collect ( ) )
112
+ }
85
113
}
86
114
87
115
fn current_package < S : PackagesSource > ( & self , source : & S ) -> Result < Option < S :: Package > > {
@@ -92,8 +120,25 @@ impl PackagesFilter {
92
120
. cloned ( ) )
93
121
}
94
122
95
- fn current_selected ( & self , spec : & Spec < ' _ > ) -> bool {
96
- !self . workspace && matches ! ( spec, Spec :: All )
123
+ fn current_selected ( & self , specs : & [ Spec < ' _ > ] ) -> bool {
124
+ !self . workspace && specs. iter ( ) . any ( |spec| matches ! ( spec, Spec :: All ) )
125
+ }
126
+
127
+ fn do_match_all < S : PackagesSource > (
128
+ specs : Vec < Spec > ,
129
+ workspace : bool ,
130
+ members : Vec < S :: Package > ,
131
+ ) -> Result < Vec < S :: Package > > {
132
+ let mut packages = Vec :: new ( ) ;
133
+ for spec in specs {
134
+ packages. extend ( Self :: do_match :: < S > (
135
+ & spec,
136
+ workspace,
137
+ members. clone ( ) . into_iter ( ) ,
138
+ ) ?) ;
139
+ }
140
+ packages. dedup_by_key ( |p| S :: package_name_of ( p) . to_string ( ) ) ;
141
+ Ok ( packages)
97
142
}
98
143
99
144
fn do_match < S : PackagesSource > (
@@ -124,6 +169,7 @@ impl PackagesFilter {
124
169
}
125
170
}
126
171
172
+ #[ derive( PartialEq , Eq , Hash ) ]
127
173
enum Spec < ' a > {
128
174
All ,
129
175
One ( & ' a str ) ,
0 commit comments