@@ -29,11 +29,11 @@ import (
29
29
// userListCmd holds the configuration for the list command
30
30
type userListCmd struct {
31
31
// Input control
32
- path [] string
32
+ path string
33
33
34
34
// Filter control
35
- features [] string
36
- missing bool
35
+ feature string
36
+ missing bool
37
37
38
38
// Output control
39
39
basic bool
@@ -48,42 +48,39 @@ type userListCmd struct {
48
48
// listCmd lists components or SBOM properties based on specified features
49
49
var listCmd = & cobra.Command {
50
50
Use : "list" ,
51
- Short : "List components or SBOM properties based on features " ,
51
+ Short : "List components or SBOM properties based on feature " ,
52
52
SilenceUsage : true ,
53
- Example : ` sbomqs list --features <features > --option <path-to-sbom-file>
53
+ Example : ` sbomqs list --feature <feature > --option <path-to-sbom-file>
54
54
55
55
# List all components with suppliers
56
- sbomqs list --features comp_with_supplier samples/sbomqs-spdx-syft.json
56
+ sbomqs list --feature comp_with_supplier samples/sbomqs-spdx-syft.json
57
57
58
58
# List all components missing suppliers
59
- sbomqs list --features comp_with_supplier --missing samples/sbomqs-spdx-syft.json
59
+ sbomqs list --feature comp_with_supplier --missing samples/sbomqs-spdx-syft.json
60
60
61
61
# List all components with valid licenses
62
- sbomqs list --features comp_valid_licenses samples/sbomqs-spdx-syft.json
62
+ sbomqs list --feature comp_valid_licenses samples/sbomqs-spdx-syft.json
63
63
64
64
# List all components with invalid licenses
65
- sbomqs list --features comp_valid_licenses --missing samples/sbomqs-spdx-syft.json
65
+ sbomqs list --feature comp_valid_licenses --missing samples/sbomqs-spdx-syft.json
66
66
67
- # List all components of SBOM with comp_with_licenses as well as comp_with_version
68
- sbomqs list --features="comp_with_licenses,comp_with_version" samples/photon.spdx.json
69
-
70
- # List all components for both SBOM with comp_with_licenses as well as comp_with_version
71
- sbomqs list --features="comp_with_licenses,comp_with_version" samples/photon.spdx.json samples/sbomqs-cdx-cgomod.json
72
-
73
- # component features:
67
+ # Component features:
74
68
[comp_with_name, comp_with_version, comp_with_supplier, comp_with_uniq_ids, comp_valid_licenses, comp_with_any_vuln_lookup_id,
75
69
comp_with_deprecated_licenses, comp_with_multi_vuln_lookup_id, comp_with_primary_purpose, comp_with_restrictive_licenses,
76
70
comp_with_checksums, comp_with_licenses]
77
71
78
- # sbom features:
72
+ # SBOM features:
79
73
[sbom_creation_timestamp, sbom_authors, sbom_with_creator_and_version, sbom_with_primary_component, sbom_dependencies,
80
- sbom_sharable, sbom_parsable, sbom_spec, sbom_spec_file_format, sbom_spec_version ]
74
+ sbom_sharable, sbom_parsable, sbom_spec, sbom_spec_file_format, sbom_spec_version]
81
75
` ,
82
76
83
77
Args : func (_ * cobra.Command , args []string ) error {
84
78
if len (args ) < 1 {
85
79
return fmt .Errorf ("requires a path to an SBOM file or directory of SBOM files" )
86
80
}
81
+ if len (args ) > 1 {
82
+ return fmt .Errorf ("only one file path is allowed, got %d: %v" , len (args ), args )
83
+ }
87
84
return nil
88
85
},
89
86
RunE : func (cmd * cobra.Command , args []string ) error {
@@ -97,7 +94,6 @@ var listCmd = &cobra.Command{
97
94
ctx := logger .WithLogger (context .Background ())
98
95
uCmd := parseListParams (cmd , args )
99
96
if err := validateparsedListCmd (uCmd ); err != nil {
100
- logger .FromContext (ctx ).Errorf ("Invalid command parameters: %v" , err )
101
97
return err
102
98
}
103
99
@@ -112,12 +108,11 @@ func parseListParams(cmd *cobra.Command, args []string) *userListCmd {
112
108
uCmd := & userListCmd {}
113
109
114
110
// Input control
115
- uCmd .path = args
111
+ uCmd .path = args [ 0 ]
116
112
117
113
// Filter control
118
- feature , _ := cmd .Flags ().GetString ("features" )
119
- features := strings .Split (feature , "," )
120
- uCmd .features = features
114
+ feature , _ := cmd .Flags ().GetString ("feature" )
115
+ uCmd .feature = feature
121
116
122
117
missing , _ := cmd .Flags ().GetBool ("missing" )
123
118
uCmd .missing = missing
@@ -130,7 +125,6 @@ func parseListParams(cmd *cobra.Command, args []string) *userListCmd {
130
125
uCmd .json = json
131
126
132
127
detailed , _ := cmd .Flags ().GetBool ("detailed" )
133
-
134
128
uCmd .detailed = detailed
135
129
136
130
color , _ := cmd .Flags ().GetBool ("color" )
@@ -145,8 +139,8 @@ func parseListParams(cmd *cobra.Command, args []string) *userListCmd {
145
139
146
140
func fromListToEngineParams (uCmd * userListCmd ) * engine.Params {
147
141
return & engine.Params {
148
- Path : uCmd .path ,
149
- Features : uCmd .features ,
142
+ Path : [] string { uCmd .path } ,
143
+ Features : [] string { uCmd .feature } ,
150
144
Missing : uCmd .missing ,
151
145
Basic : uCmd .basic ,
152
146
JSON : uCmd .json ,
@@ -160,54 +154,95 @@ func init() {
160
154
rootCmd .AddCommand (listCmd )
161
155
162
156
// Filter Control
163
- listCmd .Flags ().StringP ( "features " , "f " , "" , "filter by feature (e.g. 'sbom_authors', 'comp_with_name', 'sbom_creation_timestamp') " )
164
- err := listCmd .MarkFlagRequired ("features " )
157
+ listCmd .Flags ().String ( "feature " , "" , "Filter by feature (e.g., 'sbom_authors', 'comp_with_name', 'sbom_creation_timestamp'); if repeated, last value is used " )
158
+ err := listCmd .MarkFlagRequired ("feature " )
165
159
if err != nil {
166
160
log .Fatal (err )
167
161
}
168
- listCmd .Flags ().BoolP ("missing" , "m" , false , "list components or properties missing the specified feature" )
162
+ listCmd .Flags ().BoolP ("missing" , "m" , false , "List components or properties missing the specified feature" )
169
163
170
164
// Output Control
171
- listCmd .Flags ().BoolP ("basic" , "b" , false , "results in single-line format" )
172
- listCmd .Flags ().BoolP ("json" , "j" , false , "results in json " )
173
- listCmd .Flags ().BoolP ("detailed" , "d" , true , "results in table format, default" )
174
- listCmd .Flags ().BoolP ("color" , "l" , false , "output in colorful " )
165
+ listCmd .Flags ().BoolP ("basic" , "b" , false , "Results in single-line format" )
166
+ listCmd .Flags ().BoolP ("json" , "j" , false , "Results in JSON " )
167
+ listCmd .Flags ().BoolP ("detailed" , "d" , true , "Results in table format, default" )
168
+ listCmd .Flags ().BoolP ("color" , "l" , false , "Output in color " )
175
169
176
170
// Debug Control
177
- listCmd .Flags ().BoolP ("debug" , "D" , false , "enable debug logging" )
171
+ listCmd .Flags ().BoolP ("debug" , "D" , false , "Enable debug logging" )
172
+
173
+ // Register flag completion for --feature
174
+ err = listCmd .RegisterFlagCompletionFunc ("feature" , func (_ * cobra.Command , _ []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
175
+ var completions []string
176
+ for feature := range isFeaturePresent {
177
+ if strings .HasPrefix (feature , toComplete ) {
178
+ completions = append (completions , feature )
179
+ }
180
+ }
181
+ return completions , cobra .ShellCompDirectiveNoFileComp
182
+ })
183
+ if err != nil {
184
+ log .Fatalf ("Failed to register flag completion for --feature: %v" , err )
185
+ }
178
186
}
179
187
180
188
func validateparsedListCmd (uCmd * userListCmd ) error {
189
+ // Check path
181
190
if len (uCmd .path ) <= 0 {
182
- fmt .Println ("Error: path is required" )
183
191
return errors .New ("path is required" )
184
192
}
185
193
186
- if len (uCmd .features ) == 0 {
187
- fmt .Println ("Error: feature is required" )
188
- log .Fatal ("at least one feature must be specified" )
194
+ // Validate feature
195
+ feature := uCmd .feature
196
+ if feature == "" {
197
+ return errors .New ("feature is required" )
198
+ }
199
+
200
+ // Reject comma-separated lists or any commas
201
+ if strings .Contains (feature , "," ) {
202
+ if ! strings .HasSuffix (strings .TrimSpace (feature ), "," ) {
203
+ return fmt .Errorf ("--feature expects a single value, got comma-separated list: %q" , feature )
204
+ }
205
+ return fmt .Errorf ("--feature expects a single value, contains comma: %q" , feature )
206
+ }
207
+
208
+ // Trim spaces
209
+ cleaned := strings .TrimSpace (feature )
189
210
211
+ uCmd .feature = cleaned
212
+
213
+ // Validate against supported features
214
+ if _ , ok := isFeaturePresent [cleaned ]; ! ok {
215
+ var supportedFeatures []string
216
+ for f := range isFeaturePresent {
217
+ supportedFeatures = append (supportedFeatures , f )
218
+ }
219
+ return fmt .Errorf ("feature %q is not supported; supported features are: %s" , cleaned , strings .Join (supportedFeatures , ", " ))
190
220
}
191
- // we want to cover these cases:
192
- // 1. --feature=" comp_with_name" ---> this is totally fine as it has only 1 feature
193
- // 2. --feature=" comp_with_name " ---> this is also fine as it has only 1 feature
194
- // 3. --feature="comp_with_name comp_with_version" ---> this is not fine as it has 2 features
195
- // 4. --feature="comp_with_name, comp_with_version" ---> this is also not fine as it has 2 features
196
-
197
- // TODO: validation of feature
198
- // // Check if the feature is valid
199
- // validFeatures := []string{"comp_with_supplier", "comp_valid_licenses", "sbom_authors"}
200
- // featureFound := false
201
- // for _, validFeature := range validFeatures {
202
- // if strings.TrimSpace(uCmd.feature) == validFeature {
203
- // featureFound = true
204
- // break
205
- // }
206
- // }
207
- // if !featureFound {
208
- // fmt.Printf("Error: invalid feature '%s'. Valid features are: %v\n", uCmd.feature, validFeatures)
209
- // return fmt.Errorf("invalid feature '%s'", uCmd.feature)
210
- // }
211
221
212
222
return nil
213
223
}
224
+
225
+ var isFeaturePresent = map [string ]bool {
226
+ "comp_with_name" : true ,
227
+ "comp_with_version" : true ,
228
+ "comp_with_supplier" : true ,
229
+ "comp_with_uniq_ids" : true ,
230
+ "comp_valid_licenses" : true ,
231
+ "comp_with_any_vuln_lookup_id" : true ,
232
+ "comp_with_deprecated_licenses" : true ,
233
+ "comp_with_multi_vuln_lookup_id" : true ,
234
+ "comp_with_primary_purpose" : true ,
235
+ "comp_with_restrictive_licenses" : true ,
236
+ "comp_with_checksums" : true ,
237
+ "comp_with_licenses" : true ,
238
+ "sbom_creation_timestamp" : true ,
239
+ "sbom_authors" : true ,
240
+ "sbom_with_creator_and_version" : true ,
241
+ "sbom_with_primary_component" : true ,
242
+ "sbom_dependencies" : true ,
243
+ "sbom_sharable" : true ,
244
+ "sbom_parsable" : true ,
245
+ "sbom_spec" : true ,
246
+ "sbom_spec_file_format" : true ,
247
+ "sbom_spec_version" : true ,
248
+ }
0 commit comments