11
11
using System . Security ;
12
12
using System . Text ;
13
13
using System . Runtime . InteropServices ;
14
+ using Microsoft . Extensions . FileSystemGlobbing ;
15
+ using Microsoft . Extensions . FileSystemGlobbing . Abstractions ;
14
16
15
17
namespace Microsoft . PowerShell . EditorServices
16
18
{
@@ -22,11 +24,27 @@ public class Workspace
22
24
{
23
25
#region Private Fields
24
26
25
- private static readonly string [ ] s_psFilePatterns = new [ ]
27
+ private static readonly string [ ] s_psFileExtensions = new [ ]
26
28
{
27
- "*.ps1" ,
28
- "*.psm1" ,
29
- "*.psd1"
29
+ ".ps1" ,
30
+ ".psm1" ,
31
+ ".psd1"
32
+ } ;
33
+ // .Net Core doesn't appear to use the same three letter pattern matching rule although the docs
34
+ // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1'
35
+ // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_
36
+ private static readonly string [ ] s_psFileExtensionsDotNetFramework = new [ ]
37
+ {
38
+ ".ps1" ,
39
+ ".psm1" ,
40
+ ".psd1" ,
41
+ ".ps1xml"
42
+ } ;
43
+
44
+ // An array of globs which includes everything
45
+ private static readonly string [ ] s_psIncludeAllGlob = new [ ]
46
+ {
47
+ "**/*"
30
48
} ;
31
49
32
50
private ILogger logger ;
@@ -42,6 +60,16 @@ public class Workspace
42
60
/// </summary>
43
61
public string WorkspacePath { get ; set ; }
44
62
63
+ /// <summary>
64
+ /// Gets or sets the default list of file globs to exclude during workspace searchs
65
+ /// </summary>
66
+ public List < string > ExcludeFilesGlob { get ; set ; }
67
+
68
+ /// <summary>
69
+ /// Gets or sets the default list of file globs to exclude during workspace searchs
70
+ /// </summary>
71
+ public Boolean FollowSymlinks { get ; set ; }
72
+
45
73
#endregion
46
74
47
75
#region Constructors
@@ -55,6 +83,8 @@ public Workspace(Version powerShellVersion, ILogger logger)
55
83
{
56
84
this . powerShellVersion = powerShellVersion ;
57
85
this . logger = logger ;
86
+ this . ExcludeFilesGlob = new List < string > ( ) ;
87
+ this . FollowSymlinks = true ;
58
88
}
59
89
60
90
#endregion
@@ -282,135 +312,64 @@ public string GetRelativePath(string filePath)
282
312
}
283
313
284
314
/// <summary>
285
- /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner
315
+ /// Whether the current runtime is Dot Net Framework as opposed to Core or Native
316
+ /// </summary>
317
+ private Boolean IsDotNetFrameWork ( )
318
+ {
319
+ return RuntimeInformation . FrameworkDescription . StartsWith ( ".NET Framework" ) ;
320
+ }
321
+
322
+ /// <summary>
323
+ /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner, using default values
286
324
/// </summary>
287
325
/// <returns>An enumerator over the PowerShell files found in the workspace</returns>
288
326
public IEnumerable < string > EnumeratePSFiles ( )
289
327
{
290
- if ( WorkspacePath == null || ! Directory . Exists ( WorkspacePath ) )
291
- {
292
- return Enumerable . Empty < string > ( ) ;
293
- }
294
-
295
- var foundFiles = new List < string > ( ) ;
296
- this . RecursivelyEnumerateFiles ( WorkspacePath , ref foundFiles ) ;
297
- return foundFiles ;
328
+ return this . EnumeratePSFiles (
329
+ this . ExcludeFilesGlob . ToArray ( ) ,
330
+ s_psIncludeAllGlob ,
331
+ 64 ,
332
+ ! this . FollowSymlinks
333
+ ) ;
298
334
}
299
335
300
- #endregion
301
-
302
- #region Private Methods
303
-
304
336
/// <summary>
305
- /// Find PowerShell files recursively down from a given directory path.
306
- /// Currently collects files in depth-first order.
307
- /// Directory.GetFiles(folderPath, pattern, SearchOption.AllDirectories) would provide this,
308
- /// but a cycle in the filesystem will cause that to enter an infinite loop.
337
+ /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner
309
338
/// </summary>
310
- /// <param name="folderPath">The path of the current directory to find files in</param>
311
- /// <param name="foundFiles">The accumulator for files found so far.</param>
312
- /// <param name="currDepth">The current depth of the recursion from the original base directory.</param>
313
- private void RecursivelyEnumerateFiles ( string folderPath , ref List < string > foundFiles , int currDepth = 0 )
339
+ /// <returns>An enumerator over the PowerShell files found in the workspace</returns>
340
+ public IEnumerable < string > EnumeratePSFiles (
341
+ string [ ] excludeGlobs ,
342
+ string [ ] includeGlobs ,
343
+ int maxDepth ,
344
+ Boolean ignoreReparsePoints
345
+ )
314
346
{
315
- const int recursionDepthLimit = 64 ;
316
-
317
- // Look for any PowerShell files in the current directory
318
- foreach ( string pattern in s_psFilePatterns )
319
- {
320
- string [ ] psFiles ;
321
- try
322
- {
323
- psFiles = Directory . GetFiles ( folderPath , pattern , SearchOption . TopDirectoryOnly ) ;
324
- }
325
- catch ( DirectoryNotFoundException e )
326
- {
327
- this . logger . WriteHandledException (
328
- $ "Could not enumerate files in the path '{ folderPath } ' due to it being an invalid path",
329
- e ) ;
330
-
331
- continue ;
332
- }
333
- catch ( PathTooLongException e )
334
- {
335
- this . logger . WriteHandledException (
336
- $ "Could not enumerate files in the path '{ folderPath } ' due to the path being too long",
337
- e ) ;
338
-
339
- continue ;
340
- }
341
- catch ( Exception e ) when ( e is SecurityException || e is UnauthorizedAccessException )
342
- {
343
- this . logger . WriteHandledException (
344
- $ "Could not enumerate files in the path '{ folderPath } ' due to the path not being accessible",
345
- e ) ;
346
-
347
- continue ;
348
- }
349
- catch ( Exception e )
350
- {
351
- this . logger . WriteHandledException (
352
- $ "Could not enumerate files in the path '{ folderPath } ' due to an exception",
353
- e ) ;
354
-
355
- continue ;
356
- }
357
-
358
- foundFiles . AddRange ( psFiles ) ;
359
- }
360
-
361
- // Prevent unbounded recursion here
362
- if ( currDepth >= recursionDepthLimit )
363
- {
364
- this . logger . Write ( LogLevel . Warning , $ "Recursion depth limit hit for path { folderPath } ") ;
365
- return ;
366
- }
367
-
368
- // Add the recursive directories to search next
369
- string [ ] subDirs ;
370
- try
371
- {
372
- subDirs = Directory . GetDirectories ( folderPath ) ;
373
- }
374
- catch ( DirectoryNotFoundException e )
375
- {
376
- this . logger . WriteHandledException (
377
- $ "Could not enumerate directories in the path '{ folderPath } ' due to it being an invalid path",
378
- e ) ;
379
-
380
- return ;
381
- }
382
- catch ( PathTooLongException e )
383
- {
384
- this . logger . WriteHandledException (
385
- $ "Could not enumerate directories in the path '{ folderPath } ' due to the path being too long",
386
- e ) ;
387
-
388
- return ;
389
- }
390
- catch ( Exception e ) when ( e is SecurityException || e is UnauthorizedAccessException )
391
- {
392
- this . logger . WriteHandledException (
393
- $ "Could not enumerate directories in the path '{ folderPath } ' due to the path not being accessible",
394
- e ) ;
395
-
396
- return ;
397
- }
398
- catch ( Exception e )
347
+ if ( WorkspacePath == null || ! Directory . Exists ( WorkspacePath ) )
399
348
{
400
- this . logger . WriteHandledException (
401
- $ "Could not enumerate directories in the path '{ folderPath } ' due to an exception",
402
- e ) ;
403
-
404
- return ;
349
+ yield break ;
405
350
}
406
351
407
-
408
- foreach ( string subDir in subDirs )
352
+ var matcher = new Microsoft . Extensions . FileSystemGlobbing . Matcher ( ) ;
353
+ foreach ( string pattern in includeGlobs ) { matcher . AddInclude ( pattern ) ; }
354
+ foreach ( string pattern in excludeGlobs ) { matcher . AddExclude ( pattern ) ; }
355
+
356
+ var fsFactory = new WorkspaceFileSystemWrapperFactory (
357
+ WorkspacePath ,
358
+ maxDepth ,
359
+ this . IsDotNetFrameWork ( ) ? s_psFileExtensionsDotNetFramework : s_psFileExtensions ,
360
+ ignoreReparsePoints ,
361
+ this . logger
362
+ ) ;
363
+ var result = matcher . Execute ( fsFactory . RootDirectory ) ;
364
+ foreach ( FilePatternMatch item in result . Files )
409
365
{
410
- RecursivelyEnumerateFiles ( subDir , ref foundFiles , currDepth : currDepth + 1 ) ;
366
+ yield return Path . Combine ( WorkspacePath , item . Path . Replace ( '/' , Path . DirectorySeparatorChar ) ) ;
411
367
}
412
368
}
413
369
370
+ #endregion
371
+
372
+ #region Private Methods
414
373
/// <summary>
415
374
/// Recusrively searches through referencedFiles in scriptFiles
416
375
/// and builds a Dictonary of the file references
0 commit comments