55from dataclasses import asdict
66from glob import glob
77from pathlib import PurePath
8- from typing import BinaryIO , Dict , List , Tuple
9-
8+ from typing import BinaryIO , Dict , List , Tuple , Set
9+ import re
1010from socketdev import socketdev
1111from socketdev .exceptions import APIFailure
1212from socketdev .fullscans import FullScanParams , SocketArtifact
@@ -123,19 +123,42 @@ def create_sbom_output(self, diff: Diff) -> dict:
123123 log .error (result .get ("message" , "No error message provided" ))
124124 return {}
125125
126+ @staticmethod
127+ def expand_brace_pattern (pattern : str ) -> List [str ]:
128+ """
129+ Expands brace expressions (e.g., {a,b,c}) into separate patterns.
130+ """
131+ brace_regex = re .compile (r"\{([^{}]+)\}" )
132+
133+ # Expand all brace groups
134+ expanded_patterns = [pattern ]
135+ while any ("{" in p for p in expanded_patterns ):
136+ new_patterns = []
137+ for pat in expanded_patterns :
138+ match = brace_regex .search (pat )
139+ if match :
140+ options = match .group (1 ).split ("," ) # Extract values inside {}
141+ prefix , suffix = pat [:match .start ()], pat [match .end ():]
142+ new_patterns .extend ([prefix + opt + suffix for opt in options ])
143+ else :
144+ new_patterns .append (pat )
145+ expanded_patterns = new_patterns
146+
147+ return expanded_patterns
148+
126149 def find_files (self , path : str ) -> List [str ]:
127150 """
128151 Finds supported manifest files in the given path.
129152
130153 Args:
131- path: Path to search for manifest files
154+ path: Path to search for manifest files.
132155
133156 Returns:
134- List of found manifest file paths
157+ List of found manifest file paths.
135158 """
136159 log .debug ("Starting Find Files" )
137160 start_time = time .time ()
138- files = set ()
161+ files : Set [ str ] = set ()
139162
140163 # Get supported patterns from the API
141164 try :
@@ -149,28 +172,28 @@ def find_files(self, path: str) -> List[str]:
149172 for ecosystem in patterns :
150173 ecosystem_patterns = patterns [ecosystem ]
151174 for file_name in ecosystem_patterns :
152- pattern = Core . to_case_insensitive_regex ( ecosystem_patterns [file_name ]["pattern" ])
153- file_path = f" { path } /**/ { pattern } "
154- #log.debug(f"Globbing {file_path}")
155- glob_start = time . time ( )
156- glob_files = glob ( file_path , recursive = True )
157- for glob_file in glob_files :
158- # Only add if it's a file, not a directory
159- if glob_file not in files and os .path .isfile ( glob_file ):
160- files . add ( glob_file )
161- glob_end = time . time ( )
162- glob_total_time = glob_end - glob_start
163- #log.debug(f"Glob for pattern {file_path} took {glob_total_time:.2f} seconds" )
164-
165- log . debug ( "Finished Find Files" )
166- end_time = time . time ()
167- total_time = end_time - start_time
168- files_list = list ( files )
169- if len ( files_list ) > 5 :
170- log .debug (f"{ len ( files_list ) } Files found ( { total_time :.2f } s): { ', ' . join ( files_list [: 5 ]) } , ... " )
171- else :
172- log .debug (f"{ len ( files_list ) } Files found ( { total_time :.2f } s): { ', ' . join ( files_list )} " )
173- return list (files )
175+ original_pattern = ecosystem_patterns [file_name ]["pattern" ]
176+
177+ # Expand brace patterns
178+ expanded_patterns = Core . expand_brace_pattern ( original_pattern )
179+
180+ for pattern in expanded_patterns :
181+ case_insensitive_pattern = Core . to_case_insensitive_regex ( pattern )
182+ file_path = os .path .join ( path , "**" , case_insensitive_pattern )
183+
184+ log . debug ( f"Globbing { file_path } " )
185+ glob_start = time . time ()
186+ glob_files = glob ( file_path , recursive = True )
187+
188+ for glob_file in glob_files :
189+ if os . path . isfile ( glob_file ):
190+ files . add ( glob_file )
191+
192+ glob_end = time . time ()
193+ log .debug (f"Globbing took { glob_end - glob_start :.4f } seconds " )
194+
195+ log .debug (f"Total files found: { len ( files )} " )
196+ return sorted (files )
174197
175198 def get_supported_patterns (self ) -> Dict :
176199 """
0 commit comments