11import os
2- from typing import TYPE_CHECKING , Iterable , List , Tuple
2+ from collections import defaultdict
3+ from typing import Set , TYPE_CHECKING , Iterable , List , Tuple
34
45import pathspec
56
1819 from cycode .cli .utils .progress_bar import BaseProgressBar , ProgressBarSection
1920
2021
21- def _get_all_existing_files_in_directory (path : str ) -> List [str ]:
22- files : List [str ] = []
22+ def _walk_to_top (path : str ) -> Iterable [str ]:
23+ while os .path .dirname (path ) != path :
24+ yield path
25+ path = os .path .dirname (path )
26+
27+ if path :
28+ yield path # Include the top-level directory
29+
30+
31+ _SUPPORTED_IGNORE_PATTERN_FILES = {'.gitignore' , '.cycodeignore' }
32+
33+
34+ def _collect_top_level_ignore_files (path : str ) -> List [str ]:
35+ ignore_files = []
36+ for dir_path in _walk_to_top (path ):
37+ for ignore_file in _SUPPORTED_IGNORE_PATTERN_FILES :
38+ ignore_file_path = os .path .join (dir_path , ignore_file )
39+ if os .path .exists (ignore_file_path ):
40+ logger .debug ('Found top level ignore file: %s' , ignore_file_path )
41+ ignore_files .append (ignore_file_path )
42+ return ignore_files
43+
44+
45+ def _get_global_ignore_patterns (path : str ) -> List [str ]:
46+ ignore_patterns = []
47+ for ignore_file in _collect_top_level_ignore_files (path ):
48+ file_patterns = get_file_content (ignore_file ).splitlines ()
49+ ignore_patterns .extend (file_patterns )
50+ return ignore_patterns
51+
52+
53+ def _apply_ignore_patterns (ignore_patterns : List [str ], files : Set [str ]) -> Set [str ]:
54+ if not ignore_patterns :
55+ return files
56+
57+ path_spec = pathspec .PathSpec .from_lines (pathspec .patterns .GitWildMatchPattern , ignore_patterns )
58+ excluded_file_paths = set (path_spec .match_files (files ))
59+
60+ return files - excluded_file_paths
61+
62+
63+ def _get_all_existing_files_in_directory (path : str , * , apply_ignore_patterns : bool = True ) -> Set [str ]:
64+ files : Set [str ] = set ()
65+
66+ global_ignore_patterns = _get_global_ignore_patterns (path )
67+ path_to_ignore_patterns = defaultdict (list )
2368
2469 for root , _ , filenames in os .walk (path ):
2570 for filename in filenames :
26- files .append (os .path .join (root , filename ))
71+ filepath = os .path .join (root , filename )
72+
73+ if filepath in _SUPPORTED_IGNORE_PATTERN_FILES :
74+ logger .debug ('Found ignore file: %s' , filepath )
75+ # TODO(MarshalX): accumulate ignore pattern from previous levels
76+ path_to_ignore_patterns [root ].extend (get_file_content (filepath ).splitlines ())
77+
78+ if apply_ignore_patterns and root in path_to_ignore_patterns :
79+ filtered_paths = _apply_ignore_patterns (path_to_ignore_patterns [root ], {filepath ,})
80+ if filtered_paths :
81+ files .update (filtered_paths )
82+ else :
83+ files .add (os .path .join (root , filename ))
84+
85+ if apply_ignore_patterns :
86+ logger .debug ('Applying global ignore patterns %s' , {'global_ignore_patterns' : global_ignore_patterns })
87+ return _apply_ignore_patterns (global_ignore_patterns , files )
2788
2889 return files
2990
@@ -37,7 +98,7 @@ def _get_relevant_files_in_path(path: str, exclude_patterns: Iterable[str]) -> L
3798 if os .path .isfile (absolute_path ):
3899 return [absolute_path ]
39100
40- all_file_paths = set ( _get_all_existing_files_in_directory (absolute_path ) )
101+ all_file_paths = _get_all_existing_files_in_directory (absolute_path )
41102
42103 path_spec = pathspec .PathSpec .from_lines (pathspec .patterns .GitWildMatchPattern , exclude_patterns )
43104 excluded_file_paths = set (path_spec .match_files (all_file_paths ))
0 commit comments