@@ -17,25 +17,42 @@ use crate::settings::configuration::Configuration;
1717use crate :: settings:: pyproject:: settings_toml;
1818use crate :: settings:: { pyproject, AllSettings , Settings } ;
1919
20+ /// The configuration information from a `pyproject.toml` file.
21+ pub struct PyprojectConfig {
22+ /// The strategy used to discover the relevant `pyproject.toml` file for
23+ /// each Python file.
24+ pub strategy : PyprojectDiscoveryStrategy ,
25+ /// All settings from the `pyproject.toml` file.
26+ pub settings : AllSettings ,
27+ /// Absolute path to the `pyproject.toml` file. This would be `None` when
28+ /// either using the default settings or the `--isolated` flag is set.
29+ pub path : Option < PathBuf > ,
30+ }
31+
32+ impl PyprojectConfig {
33+ pub fn new (
34+ strategy : PyprojectDiscoveryStrategy ,
35+ settings : AllSettings ,
36+ path : Option < PathBuf > ,
37+ ) -> Self {
38+ Self {
39+ strategy,
40+ settings,
41+ path : path. map ( fs:: normalize_path) ,
42+ }
43+ }
44+ }
45+
2046/// The strategy used to discover the relevant `pyproject.toml` file for each
2147/// Python file.
2248#[ derive( Debug , is_macro:: Is ) ]
23- pub enum PyprojectDiscovery {
49+ pub enum PyprojectDiscoveryStrategy {
2450 /// Use a fixed `pyproject.toml` file for all Python files (i.e., one
2551 /// provided on the command-line).
26- Fixed ( AllSettings ) ,
52+ Fixed ,
2753 /// Use the closest `pyproject.toml` file in the filesystem hierarchy, or
2854 /// the default settings.
29- Hierarchical ( AllSettings ) ,
30- }
31-
32- impl PyprojectDiscovery {
33- pub fn top_level_settings ( & self ) -> & AllSettings {
34- match self {
35- PyprojectDiscovery :: Fixed ( settings) => settings,
36- PyprojectDiscovery :: Hierarchical ( settings) => settings,
37- }
38- }
55+ Hierarchical ,
3956}
4057
4158/// The strategy for resolving file paths in a `pyproject.toml`.
@@ -75,21 +92,25 @@ impl Resolver {
7592 pub fn resolve_all < ' a > (
7693 & ' a self ,
7794 path : & Path ,
78- strategy : & ' a PyprojectDiscovery ,
95+ pyproject_config : & ' a PyprojectConfig ,
7996 ) -> & ' a AllSettings {
80- match strategy {
81- PyprojectDiscovery :: Fixed ( settings ) => settings,
82- PyprojectDiscovery :: Hierarchical ( default ) => self
97+ match pyproject_config . strategy {
98+ PyprojectDiscoveryStrategy :: Fixed => & pyproject_config . settings ,
99+ PyprojectDiscoveryStrategy :: Hierarchical => self
83100 . settings
84101 . iter ( )
85102 . rev ( )
86103 . find_map ( |( root, settings) | path. starts_with ( root) . then_some ( settings) )
87- . unwrap_or ( default ) ,
104+ . unwrap_or ( & pyproject_config . settings ) ,
88105 }
89106 }
90107
91- pub fn resolve < ' a > ( & ' a self , path : & Path , strategy : & ' a PyprojectDiscovery ) -> & ' a Settings {
92- & self . resolve_all ( path, strategy) . lib
108+ pub fn resolve < ' a > (
109+ & ' a self ,
110+ path : & Path ,
111+ pyproject_config : & ' a PyprojectConfig ,
112+ ) -> & ' a Settings {
113+ & self . resolve_all ( path, pyproject_config) . lib
93114 }
94115
95116 /// Return an iterator over the resolved [`Settings`] in this [`Resolver`].
@@ -200,7 +221,7 @@ fn match_exclusion<P: AsRef<Path>, R: AsRef<Path>>(
200221/// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths.
201222pub fn python_files_in_path (
202223 paths : & [ PathBuf ] ,
203- pyproject_strategy : & PyprojectDiscovery ,
224+ pyproject_config : & PyprojectConfig ,
204225 processor : impl ConfigProcessor ,
205226) -> Result < ( Vec < Result < DirEntry , ignore:: Error > > , Resolver ) > {
206227 // Normalize every path (e.g., convert from relative to absolute).
@@ -209,7 +230,7 @@ pub fn python_files_in_path(
209230 // Search for `pyproject.toml` files in all parent directories.
210231 let mut resolver = Resolver :: default ( ) ;
211232 let mut seen = FxHashSet :: default ( ) ;
212- if pyproject_strategy . is_hierarchical ( ) {
233+ if pyproject_config . strategy . is_hierarchical ( ) {
213234 for path in & paths {
214235 for ancestor in path. ancestors ( ) {
215236 if seen. insert ( ancestor) {
@@ -224,8 +245,8 @@ pub fn python_files_in_path(
224245 }
225246
226247 // Check if the paths themselves are excluded.
227- if pyproject_strategy . top_level_settings ( ) . lib . force_exclude {
228- paths. retain ( |path| !is_file_excluded ( path, & resolver, pyproject_strategy ) ) ;
248+ if pyproject_config . settings . lib . force_exclude {
249+ paths. retain ( |path| !is_file_excluded ( path, & resolver, pyproject_config ) ) ;
229250 if paths. is_empty ( ) {
230251 return Ok ( ( vec ! [ ] , resolver) ) ;
231252 }
@@ -240,12 +261,7 @@ pub fn python_files_in_path(
240261 for path in & paths[ 1 ..] {
241262 builder. add ( path) ;
242263 }
243- builder. standard_filters (
244- pyproject_strategy
245- . top_level_settings ( )
246- . lib
247- . respect_gitignore ,
248- ) ;
264+ builder. standard_filters ( pyproject_config. settings . lib . respect_gitignore ) ;
249265 builder. hidden ( false ) ;
250266 let walker = builder. build_parallel ( ) ;
251267
@@ -261,7 +277,7 @@ pub fn python_files_in_path(
261277 if entry. depth ( ) > 0 {
262278 let path = entry. path ( ) ;
263279 let resolver = resolver. read ( ) . unwrap ( ) ;
264- let settings = resolver. resolve ( path, pyproject_strategy ) ;
280+ let settings = resolver. resolve ( path, pyproject_config ) ;
265281 if let Some ( file_name) = path. file_name ( ) {
266282 if !settings. exclude . is_empty ( )
267283 && match_exclusion ( path, file_name, & settings. exclude )
@@ -283,7 +299,7 @@ pub fn python_files_in_path(
283299
284300 // Search for the `pyproject.toml` file in this directory, before we visit any
285301 // of its contents.
286- if pyproject_strategy . is_hierarchical ( ) {
302+ if pyproject_config . strategy . is_hierarchical ( ) {
287303 if let Ok ( entry) = & result {
288304 if entry
289305 . file_type ( )
@@ -321,7 +337,7 @@ pub fn python_files_in_path(
321337 // Otherwise, check if the file is included.
322338 let path = entry. path ( ) ;
323339 let resolver = resolver. read ( ) . unwrap ( ) ;
324- let settings = resolver. resolve ( path, pyproject_strategy ) ;
340+ let settings = resolver. resolve ( path, pyproject_config ) ;
325341 if settings. include . is_match ( path) {
326342 debug ! ( "Included path via `include`: {:?}" , path) ;
327343 true
@@ -348,10 +364,10 @@ pub fn python_files_in_path(
348364/// Return `true` if the Python file at [`Path`] is _not_ excluded.
349365pub fn python_file_at_path (
350366 path : & Path ,
351- pyproject_strategy : & PyprojectDiscovery ,
367+ pyproject_config : & PyprojectConfig ,
352368 processor : impl ConfigProcessor ,
353369) -> Result < bool > {
354- if !pyproject_strategy . top_level_settings ( ) . lib . force_exclude {
370+ if !pyproject_config . settings . lib . force_exclude {
355371 return Ok ( true ) ;
356372 }
357373
@@ -360,7 +376,7 @@ pub fn python_file_at_path(
360376
361377 // Search for `pyproject.toml` files in all parent directories.
362378 let mut resolver = Resolver :: default ( ) ;
363- if pyproject_strategy . is_hierarchical ( ) {
379+ if pyproject_config . strategy . is_hierarchical ( ) {
364380 for ancestor in path. ancestors ( ) {
365381 if let Some ( pyproject) = settings_toml ( ancestor) ? {
366382 let ( root, settings) =
@@ -371,14 +387,14 @@ pub fn python_file_at_path(
371387 }
372388
373389 // Check exclusions.
374- Ok ( !is_file_excluded ( & path, & resolver, pyproject_strategy ) )
390+ Ok ( !is_file_excluded ( & path, & resolver, pyproject_config ) )
375391}
376392
377393/// Return `true` if the given top-level [`Path`] should be excluded.
378394fn is_file_excluded (
379395 path : & Path ,
380396 resolver : & Resolver ,
381- pyproject_strategy : & PyprojectDiscovery ,
397+ pyproject_strategy : & PyprojectConfig ,
382398) -> bool {
383399 // TODO(charlie): Respect gitignore.
384400 for path in path. ancestors ( ) {
@@ -419,7 +435,7 @@ mod tests {
419435
420436 use crate :: resolver:: {
421437 is_file_excluded, match_exclusion, resolve_settings_with_processor, NoOpProcessor ,
422- PyprojectDiscovery , Relativity , Resolver ,
438+ PyprojectConfig , PyprojectDiscoveryStrategy , Relativity , Resolver ,
423439 } ;
424440 use crate :: settings:: pyproject:: find_settings_toml;
425441 use crate :: settings:: types:: FilePattern ;
@@ -560,25 +576,29 @@ mod tests {
560576 fn rooted_exclusion ( ) -> Result < ( ) > {
561577 let package_root = test_resource_path ( "package" ) ;
562578 let resolver = Resolver :: default ( ) ;
563- let ppd = PyprojectDiscovery :: Hierarchical ( resolve_settings_with_processor (
564- & find_settings_toml ( & package_root) ?. unwrap ( ) ,
565- & Relativity :: Parent ,
566- & NoOpProcessor ,
567- ) ?) ;
579+ let pyproject_config = PyprojectConfig :: new (
580+ PyprojectDiscoveryStrategy :: Hierarchical ,
581+ resolve_settings_with_processor (
582+ & find_settings_toml ( & package_root) ?. unwrap ( ) ,
583+ & Relativity :: Parent ,
584+ & NoOpProcessor ,
585+ ) ?,
586+ None ,
587+ ) ;
568588 // src/app.py should not be excluded even if it lives in a hierarchy that should
569589 // be excluded by virtue of the pyproject.toml having `resources/*` in
570590 // it.
571591 assert ! ( !is_file_excluded(
572592 & package_root. join( "src/app.py" ) ,
573593 & resolver,
574- & ppd ,
594+ & pyproject_config ,
575595 ) ) ;
576596 // However, resources/ignored.py should be ignored, since that `resources` is
577597 // beneath the package root.
578598 assert ! ( is_file_excluded(
579599 & package_root. join( "resources/ignored.py" ) ,
580600 & resolver,
581- & ppd ,
601+ & pyproject_config ,
582602 ) ) ;
583603 Ok ( ( ) )
584604 }
0 commit comments