77import argparse
88import os
99import sys
10+ from enum import Enum
1011from pathlib import Path
1112from typing import Optional
1213
2021logger = get_logger (__name__ )
2122
2223
24+ class FilterType (Enum ):
25+ """Types of filtering."""
26+
27+ BLOCK_ONLY_PATH_TRAVERSAL = 0
28+ BLOCK_IF_INSIDE = 1
29+ BLOCK_IF_OUTSIDE = 2
30+
31+
2332class Filter (dfetch .commands .command .Command ):
2433 """Filter files based on flags and pass on any command.
2534
@@ -33,11 +42,19 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None
3342 """Add the parser menu for this action."""
3443 parser = dfetch .commands .command .Command .parser (subparsers , Filter )
3544 parser .add_argument (
36- "--in-manifest" ,
37- "-i" ,
45+ "--dfetched" ,
46+ "-D" ,
47+ action = "store_true" ,
48+ default = True ,
49+ help = "Keep files that came here by dfetching them." ,
50+ )
51+
52+ parser .add_argument (
53+ "--not-dfetched" ,
54+ "-N" ,
3855 action = "store_true" ,
3956 default = False ,
40- help = "Keep files that came here through the manifest ." ,
57+ help = "Keep files that did not came here by dfetching them ." ,
4158 )
4259
4360 parser .add_argument (
@@ -60,83 +77,128 @@ def __call__(self, args: argparse.Namespace) -> None:
6077 """Perform the filter."""
6178 if not args .verbose :
6279 dfetch .log .set_level ("ERROR" )
63- manifest = dfetch .manifest .manifest .get_manifest ()
6480
65- pwd = Path .cwd ()
81+ argument_list = self ._get_arguments (args )
82+
83+ manifest = dfetch .manifest .manifest .get_manifest ()
6684 topdir = Path (manifest .path ).parent
67- with in_directory (topdir ):
6885
69- project_paths = {
86+ resolved_args = self ._resolve_args (argument_list , topdir )
87+
88+ with in_directory (topdir ):
89+ abs_project_paths = {
7090 Path (project .destination ).resolve () for project in manifest .projects
7191 }
7292
73- input_list = self . _determine_input_list ( args )
74- block_inside , block_outside = self . _filter_files (
75- pwd , topdir , project_paths , input_list
76- )
77-
78- blocklist = block_outside if args . in_manifest else block_inside
93+ if args . dfetched and not args . not_dfetched :
94+ block_type = FilterType . BLOCK_IF_OUTSIDE
95+ elif args . not_dfetched :
96+ block_type = FilterType . BLOCK_IF_INSIDE
97+ else :
98+ block_type = FilterType . BLOCK_ONLY_PATH_TRAVERSAL
7999
80- filtered_args = [arg for arg in input_list if arg not in blocklist ]
100+ filtered_args = self ._filter_args (
101+ topdir , resolved_args , abs_project_paths , block_type
102+ )
81103
82104 if args .cmd :
83105 run_on_cmdline_uncaptured (logger , [args .cmd ] + filtered_args )
84106 else :
85107 print (os .linesep .join (filtered_args ))
86108
87- def _determine_input_list (self , args : argparse .Namespace ) -> list [str ]:
88- """Determine list of inputs to process."""
89- input_list : list [str ] = list (str (arg ) for arg in args .args )
90- if not sys .stdin .isatty ():
91- input_list += list (str (arg ).strip () for arg in sys .stdin .readlines ())
109+ def _filter_args (
110+ self ,
111+ topdir : Path ,
112+ resolved_args : dict [str , Optional [Path ]],
113+ abs_project_paths : set [Path ],
114+ block : FilterType ,
115+ ) -> list [str ]:
116+ blocklist = self ._filter_files (
117+ topdir ,
118+ abs_project_paths ,
119+ {path for path in resolved_args .values () if path },
120+ block ,
121+ )
92122
93- # If no input from stdin or args loop over all files
94- if not input_list :
95- input_list = list (
96- str (file ) for file in Path ("." ).rglob ("*" ) if file .is_file ()
123+ filtered_args = [
124+ arg for arg in resolved_args .keys () if resolved_args [arg ] not in blocklist
125+ ]
126+
127+ return filtered_args
128+
129+ def _resolve_args (
130+ self , argument_list : list [str ], topdir : Path
131+ ) -> dict [str , Optional [Path ]]:
132+ resolved_args : dict [str , Optional [Path ]] = {}
133+ if argument_list :
134+ for argument in argument_list :
135+ path_obj = Path (argument .strip ())
136+ resolved_args [argument ] = (
137+ path_obj .resolve () if path_obj .exists () else None
138+ )
139+ else :
140+ if not argument_list :
141+ resolved_args = {
142+ str (file ): file .resolve ()
143+ for file in topdir .rglob ("*" )
144+ if ".git" not in file .parts
145+ }
146+
147+ return resolved_args
148+
149+ def _get_arguments (self , args : argparse .Namespace ) -> list [str ]:
150+ argument_list : list [str ] = list (str (arg ) for arg in args .args )
151+ if not sys .stdin .isatty ():
152+ argument_list .extend (
153+ non_empty_line for line in sys .stdin if (non_empty_line := line .strip ())
97154 )
98155
99- return input_list
156+ return argument_list
100157
101158 def _filter_files (
102- self , pwd : Path , topdir : Path , project_paths : set [Path ], input_list : list [str ]
103- ) -> tuple [list [str ], list [str ]]:
104- """Filter files in input_set in files in one of the project_paths or not."""
105- block_inside : list [str ] = []
106- block_outside : list [str ] = []
107-
108- for path_or_arg in input_list :
109- arg_abs_path = Path (pwd / path_or_arg .strip ()).resolve ()
110- if not arg_abs_path .exists ():
111- logger .print_info_line (path_or_arg .strip (), "not a file / dir" )
112- continue
159+ self ,
160+ topdir : Path ,
161+ paths : set [Path ],
162+ input_paths : set [Path ],
163+ block : FilterType = FilterType .BLOCK_IF_OUTSIDE ,
164+ ) -> list [Path ]:
165+ """Filter files in input_set in files in one of the paths or not."""
166+ blocklist : list [Path ] = []
167+
168+ for abs_path in input_paths :
113169 try :
114- arg_abs_path .relative_to (topdir )
170+ abs_path .relative_to (topdir )
115171 except ValueError :
116- logger .print_info_line (path_or_arg .strip (), "outside project" )
117- block_inside .append (path_or_arg )
118- block_outside .append (path_or_arg )
172+ logger .print_info_line (str (abs_path ), "outside project" )
173+ blocklist .append (abs_path )
174+ continue
175+
176+ if block == FilterType .BLOCK_ONLY_PATH_TRAVERSAL :
119177 continue
120178
121- containing_dir = self ._file_in_project ( arg_abs_path , project_paths )
179+ containing_dir = self ._is_file_contained_in_any_path ( abs_path , paths )
122180
123181 if containing_dir :
124- block_inside .append (path_or_arg )
125182 logger .print_info_line (
126- path_or_arg . strip ( ), f"inside project ({ containing_dir } )"
183+ str ( abs_path ), f"inside project ({ containing_dir } )"
127184 )
185+ if block == FilterType .BLOCK_IF_INSIDE :
186+ blocklist .append (abs_path )
128187 else :
129- block_outside .append (path_or_arg )
130- logger .print_info_line (path_or_arg .strip (), "not inside any project" )
188+ logger .print_info_line (str (abs_path ), "not inside any project" )
189+ if block == FilterType .BLOCK_IF_OUTSIDE :
190+ blocklist .append (abs_path )
131191
132- return block_inside , block_outside
192+ return blocklist
133193
134- def _file_in_project (self , file : Path , project_paths : set [Path ]) -> Optional [Path ]:
135- """Check if a specific file is somewhere in one of the project paths."""
136- for project_path in project_paths :
194+ def _is_file_contained_in_any_path (
195+ self , file : Path , paths : set [Path ]
196+ ) -> Optional [Path ]:
197+ """Check if a specific file is somewhere in one of the paths."""
198+ for path in paths :
137199 try :
138- file .relative_to (project_path )
139- return project_path
200+ file .relative_to (path )
201+ return path
140202 except ValueError :
141203 continue
142204 return None
0 commit comments