diff --git a/CHANGELOG.md b/CHANGELOG.md index a515203..959a5fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,79 @@ # CHANGELOG +- __01/19/2025__ + - Big code refactoring, this update improves readability and simplicity. + - Updates to `-Include` and `-Exclude` parameters, with this update the patterns are evaluated using the + object's `.Name` property instead of `.FullName`. + - In addition to the above, this update improves how the cmdlet displays trees when `-Include` is used. + Before, the cmdlet would display trees where no file was matched by the include patterns. Now, only trees having files matched by the include patterns are displayed. + + ```powershell + # PSTree v2.2.0 + PS ..\pwsh> Get-PSTree ..\PSTree -Include *.ps1, *.cs -Exclude *tools, *output + + Source: C:\User\PSTree + + Mode Length Hierarchy + ---- ------ --------- + d---- 29.57 KB PSTree + -a--- 1.34 KB ├── build.ps1 + d---- 0.00 B ├── .github + d---- 4.10 KB │ └── workflows + d---- 4.11 KB ├── .vscode + d---- 229.32 KB ├── assets + d---- 0.00 B ├── docs + d---- 12.55 KB │ └── en-US + d---- 13.63 KB ├── module + d---- 0.00 B ├── src + d---- 11.50 KB │ └── PSTree + -a--- 1.06 KB │ ├── Cache.cs + -a--- 2.65 KB │ ├── CommandWithPathBase.cs + -a--- 2.98 KB │ ├── PSTreeDirectory.cs + -a--- 1.42 KB │ ├── PSTreeFile.cs + -a--- 1.69 KB │ ├── PSTreeFileSystemInfo_T.cs + -a--- 524.00 B │ ├── PSTreeFileSystemInfo.cs + -a--- 404.00 B │ ├── TreeComparer.cs + d---- 0.00 B │ ├── bin + d---- 6.54 KB │ ├── Commands + d---- 3.63 KB │ ├── Extensions + d---- 1.14 KB │ ├── Internal + d---- 16.83 KB │ ├── obj + d---- 9.28 KB │ └── Style + d---- 17.87 KB └── tests + -a--- 765.00 B ├── FormattingInternals.tests.ps1 + -a--- 6.15 KB ├── GetPSTreeCommand.tests.ps1 + -a--- 1.77 KB ├── PSTreeDirectory.tests.ps1 + -a--- 920.00 B ├── PSTreeFile.tests.ps1 + -a--- 2.63 KB ├── PSTreeFileSystemInfo_T.tests.ps1 + -a--- 4.90 KB └── TreeStyle.tests.ps1 + + # PSTree v2.2.1 + PS ..\pwsh> Get-PSTree ..\PSTree -Include *.ps1, *.cs -Exclude tools, output + + Source: C:\User\PSTree + + Mode Length Hierarchy + ---- ------ --------- + d---- 1.34 KB PSTree + -a--- 1.34 KB ├── build.ps1 + d---- 0.00 B ├── src + d---- 10.70 KB │ └── PSTree + -a--- 1.06 KB │ ├── Cache.cs + -a--- 2.65 KB │ ├── CommandWithPathBase.cs + -a--- 2.98 KB │ ├── PSTreeDirectory.cs + -a--- 1.42 KB │ ├── PSTreeFile.cs + -a--- 1.69 KB │ ├── PSTreeFileSystemInfo_T.cs + -a--- 524.00 B │ ├── PSTreeFileSystemInfo.cs + -a--- 404.00 B │ └── TreeComparer.cs + d---- 17.10 KB └── tests + -a--- 765.00 B ├── FormattingInternals.tests.ps1 + -a--- 6.15 KB ├── GetPSTreeCommand.tests.ps1 + -a--- 1.77 KB ├── PSTreeDirectory.tests.ps1 + -a--- 920.00 B ├── PSTreeFile.tests.ps1 + -a--- 2.63 KB ├── PSTreeFileSystemInfo_T.tests.ps1 + -a--- 4.90 KB └── TreeStyle.tests.ps1 + ``` + - __09/12/2024__ - Added `TreeStyle` type and `Get-PSTreeStyle` cmdlet for rendering output. - Added Pester tests for `TreeStyle`. @@ -8,7 +82,7 @@ - __09/03/2024__ - Makes `Depth` property public for `PSTreeFileSystemInfo` instances. - Makes `GetParents()` method private, absolutely no reason to have it public. - - Added properties `ItemCount` and `TotalItemCount` to `PSTreeDirectory` instances, requested in [__Issue #34__][2]. + - Added properties `ItemCount` and `TotalItemCount` to `PSTreeDirectory` instances, requested in [__Issue #34__][21]. ```powershell PS ..\PSTree> pstree -Recurse -Force -Directory | Select-Object Hierarchy, Depth, ItemCount, TotalItemCount -First 15 @@ -275,4 +349,4 @@ d---- └── Format 1.83 Kb [18]: https://github.com/jborean93/ [19]: /docs/en-US/Get-PSTree.md [20]: https://www.powershellgallery.com/ -[2]: https://github.com/santisq/PSTree/issues/34 +[21]: https://github.com/santisq/PSTree/issues/34 diff --git a/README.md b/README.md index bdfbed6..eb24db9 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Compatible with __Windows PowerShell v5.1__ and [__PowerShell 7+__](https://gith ```powershell PS ..\PSTree> Get-PSTree | Select-Object -First 20 - Source: C:\path\to\PSTree + Source: C:\User\Documents\PSTree Mode Length Hierarchy ---- ------ --------- @@ -77,71 +77,61 @@ d---- 0.00 B ├── src ### Exclude `tools` and `tests` folders ```powershell -PS ..\PSTree> Get-PSTree -Exclude *tools, *tests | Select-Object -First 20 +PS ..\PSTree> Get-PSTree -Exclude tools, tests | Select-Object -First 20 - Source: C:\path\to\PSTree + Source: C:\User\Documents\PSTree Mode Length Hierarchy ---- ------ --------- -d---- 31.20 KB PSTree --a--- 4.64 KB ├── .gitignore +d---- 33.23 KB PSTree +-a--- 4.75 KB ├── .gitignore -a--- 137.00 B ├── .markdownlint.json --a--- 2.16 KB ├── build.ps1 --a--- 7.90 KB ├── CHANGELOG.md +-a--- 1.34 KB ├── build.ps1 +-a--- 18.08 KB ├── CHANGELOG.md -a--- 1.07 KB ├── LICENSE --a--- 8.10 KB ├── PSTree.build.ps1 --a--- 5.96 KB ├── README.md --a--- 1.23 KB ├── ScriptAnalyzerSettings.psd1 -d---- 0.00 B ├── src -d---- 10.30 KB │ └── PSTree --a--- 931.00 B │ ├── ExceptionHelpers.cs --a--- 439.00 B │ ├── PSTree.csproj --a--- 1.06 KB │ ├── PSTreeDirectory.cs --a--- 4.01 KB │ ├── PSTreeExtensions.cs --a--- 517.00 B │ ├── PSTreeFile.cs --a--- 399.00 B │ ├── PSTreeFileSystemInfo.cs --a--- 1.51 KB │ ├── PSTreeFileSystemInfo_T.cs --a--- 897.00 B │ ├── PSTreeHelper.cs --a--- 619.00 B │ ├── PSTreeIndexer.cs +-a--- 7.85 KB ├── README.md +d---- 0.00 B ├── .github +d---- 4.10 KB │ └── workflows +-a--- 4.10 KB │ └── ci.yml +d---- 4.11 KB ├── .vscode +-a--- 275.00 B │ ├── extensions.json +-a--- 1.39 KB │ ├── launch.json +-a--- 1.02 KB │ ├── settings.json +-a--- 1.43 KB │ └── tasks.json +d---- 229.32 KB ├── assets +-a--- 10.00 KB │ ├── EscapeSequence.png +-a--- 78.08 KB │ ├── Example.After.png +-a--- 73.89 KB │ ├── Example.Before.png +-a--- 67.35 KB │ └── TreeStyle.png ``` -### Include `.ps1` and `.cs` files and exclude some folders +### Include `.ps1` and `.cs` files and exclude `tools` folder ```powershell -PS ..\PSTree> Get-PStree -Include *.ps1, *.cs -Exclude *output, *tools, *docs, *module +PS ..\PSTree> Get-PStree -Include *.ps1, *.cs -Exclude tools - Source: C:\path\to\PSTree + Source: C:\User\Documents\PSTree Mode Length Hierarchy ---- ------ --------- -d---- 33.15 KB PSTree --a--- 2.35 KB ├── build.ps1 --a--- 8.10 KB ├── PSTree.build.ps1 -d---- 13.29 KB ├── tests --a--- 765.00 B │ ├── FormattingInternals.tests.ps1 --a--- 5.89 KB │ ├── GetPSTreeCommand.tests.ps1 --a--- 1.51 KB │ ├── PathExtensions.tests.ps1 --a--- 1.38 KB │ ├── PSTreeDirectory.ps1 --a--- 920.00 B │ ├── PSTreeFile.tests.ps1 --a--- 2.09 KB │ └── PSTreeFileSystemInfo_T.tests.ps1 +d---- 1.34 KB PSTree +-a--- 1.34 KB ├── build.ps1 d---- 0.00 B ├── src -d---- 12.15 KB │ └── PSTree --a--- 931.00 B │ ├── ExceptionHelpers.cs --a--- 4.09 KB │ ├── PathExtensions.cs --a--- 900.00 B │ ├── PSTreeCache.cs --a--- 1.06 KB │ ├── PSTreeDirectory.cs --a--- 1.66 KB │ ├── PSTreeExtensions.cs --a--- 517.00 B │ ├── PSTreeFile.cs --a--- 399.00 B │ ├── PSTreeFileSystemInfo.cs --a--- 1.61 KB │ ├── PSTreeFileSystemInfo_T.cs --a--- 626.00 B │ ├── PSTreeIndexer.cs -d---- 16.53 KB │ ├── obj -d---- 1.15 KB │ ├── Internal -d---- 6.43 KB │ ├── Commands -d---- 0.00 B │ └── bin -d---- 4.07 KB ├── .vscode -d---- 0.00 B └── .github -d---- 4.17 KB └── workflows +d---- 10.70 KB │ └── PSTree +-a--- 1.06 KB │ ├── Cache.cs +-a--- 2.65 KB │ ├── CommandWithPathBase.cs +-a--- 2.98 KB │ ├── PSTreeDirectory.cs +-a--- 1.42 KB │ ├── PSTreeFile.cs +-a--- 1.69 KB │ ├── PSTreeFileSystemInfo_T.cs +-a--- 524.00 B │ ├── PSTreeFileSystemInfo.cs +-a--- 404.00 B │ └── TreeComparer.cs +d---- 17.10 KB └── tests +-a--- 765.00 B ├── FormattingInternals.tests.ps1 +-a--- 6.15 KB ├── GetPSTreeCommand.tests.ps1 +-a--- 1.77 KB ├── PSTreeDirectory.tests.ps1 +-a--- 920.00 B ├── PSTreeFile.tests.ps1 +-a--- 2.63 KB ├── PSTreeFileSystemInfo_T.tests.ps1 +-a--- 4.90 KB └── TreeStyle.tests.ps1 ``` ### Get the `src` tree recursively displaying only folders @@ -149,21 +139,23 @@ d---- 4.17 KB └── workflows ```powershell PS ..\PSTree> Get-PSTree .\src\ -Recurse -Directory - Source: C:\path\to\PSTree\src + Source: C:\User\Documents\PSTree\src Mode Length Hierarchy ---- ------ --------- d---- 0.00 B src -d---- 10.30 KB └── PSTree -d---- 16.53 KB ├── obj +d---- 11.50 KB └── PSTree +d---- 0.00 B ├── bin d---- 0.00 B │ └── Debug -d---- 88.02 KB │ └── netstandard2.0 -d---- 1.13 KB ├── Internal -d---- 5.68 KB ├── Commands -d---- 0.00 B └── bin -d---- 0.00 B └── Debug -d---- 33.31 KB └── netstandard2.0 -d---- 33.11 KB └── publish +d---- 56.49 KB │ └── netstandard2.0 +d---- 56.29 KB │ └── publish +d---- 6.54 KB ├── Commands +d---- 3.63 KB ├── Extensions +d---- 1.14 KB ├── Internal +d---- 16.83 KB ├── obj +d---- 0.00 B │ └── Debug +d---- 112.44 KB │ └── netstandard2.0 +d---- 9.28 KB └── Style ``` ### Display subdirectories only 2 levels deep @@ -171,7 +163,7 @@ d---- 33.11 KB └── publish ```powershell PS ..\PSTree> Get-PSTree .\src\ -Depth 2 -Directory - Source: C:\path\to\PSTree\src + Source: C:\User\Documents\PSTree\src Mode Length Hierarchy ---- ------ --------- @@ -188,7 +180,7 @@ d---- 0.00 B └── bin ```powershell PS ..\PSTree> Get-PSTree .\src\ -Depth 2 -Directory -RecursiveSize - Source: C:\path\to\PSTree\src + Source: C:\User\Documents\PSTree\src Mode Length Hierarchy ---- ------ --------- @@ -207,4 +199,4 @@ d---- 66.42 KB └── bin ## Contributing -Contributions are more than welcome, if you wish to contribute, fork this repository and submit a pull request with the changes. +Contributions are welcome, if you wish to contribute, fork this repository and submit a pull request with the changes. diff --git a/docs/en-US/Get-PSTree.md b/docs/en-US/Get-PSTree.md index b7d6e1a..7a554a2 100644 --- a/docs/en-US/Get-PSTree.md +++ b/docs/en-US/Get-PSTree.md @@ -18,7 +18,7 @@ schema: 2.0.0 ```powershell Get-PSTree [[-Path] ] - [-Depth ] + [-Depth ] [-Recurse] [-Force] [-Directory] @@ -33,7 +33,7 @@ Get-PSTree ```powershell Get-PSTree [-LiteralPath ] - [-Depth ] + [-Depth ] [-Recurse] [-Force] [-Directory] @@ -71,7 +71,8 @@ In this example `$HOME` is bound positionally to the `-Path` parameter. PS ..\PSTree> Get-PSTree -Depth 2 -Force ``` -The `-Force` switch is needed to display hidden files and folders. In addition, hidden child items do not add up to the folders size without this switch. +> [!TIP] +> The `-Force` switch is needed to display hidden files and folders. In addition, hidden child items do not add up to the folders size without this switch. ### Example 4: Get the `C:\` drive tree 2 levels in depth displaying only folders calculating the recursive size @@ -85,7 +86,10 @@ PS ..\PSTree> Get-PSTree C:\ -Depth 2 -RecursiveSize -Directory PS ..\PSTree> Get-PSTree $HOME -Recurse -Exclude *.jpg, *.png ``` -The `-Exclude` parameter supports [wildcard patterns](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.3), exclusion patterns are tested against the items `.FullName` property. Excluded items do not do not add to the folders size. +> [!NOTE] +> +> - The `-Exclude` parameter supports [wildcard patterns](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.3), exclusion patterns are evaluated using the items `.Name` property. +> - __Excluded items do not do not add to the folders size.__ ### Example 6: Get the tree of all folders in a location @@ -93,7 +97,8 @@ The `-Exclude` parameter supports [wildcard patterns](https://learn.microsoft.co PS ..\PSTree> Get-ChildItem -Directory | Get-PSTree ``` -`DirectoryInfo` and `FileInfo` instances having the `PSPath` Property are bound to the `-LiteralPath` parameter. +> [!TIP] +> Output from `Get-ChildItem` can be piped to this cmdlet. Pipeline input is bound to `-LiteralPath` parameter if the items have a `PSPath` property. ### Example 7: Get the tree of all folders in a location including only `*.ps1` files @@ -101,7 +106,8 @@ PS ..\PSTree> Get-ChildItem -Directory | Get-PSTree PS ..\PSTree> Get-ChildItem -Directory | Get-PSTree -Include *.ps1 ``` -Similar to `-Exclude`, the `-Include` parameter supports [wildcard patterns](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.3), however, __this parameter works only with Files__. +> [!IMPORTANT] +> Similar to `-Exclude`, the `-Include` parameter supports [wildcard patterns](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.3), however, __this parameter works only with Files__. ## PARAMETERS @@ -110,7 +116,7 @@ Similar to `-Exclude`, the `-Include` parameter supports [wildcard patterns](htt Determines the number of subdirectory levels that are included in the recursion. ```yaml -Type: UInt32 +Type: Int32 Parameter Sets: (All) Aliases: @@ -147,8 +153,8 @@ Excluded items do not add to the recursive folders size. > [!NOTE] > -> - Patterns are tested against the object's `.FullName` property. -> - The `-Include` and `-Exclude` parameters can be used together and the inclusions are applied after the exclusions. +> - Patterns are evaluated using the object's `.Name` property. +> - The `-Include` and `-Exclude` parameters can be used together, however the exclusions are applied before the inclusions. ```yaml Type: String[] @@ -186,9 +192,9 @@ Wildcard characters are accepted. > [!NOTE] > -> - Patterns are tested against the object's `.FullName` property. -> - This parameter focuses only on files, the inclusion patterns are only evaluated against `FileInfo` instances. -> - The `-Include` and `-Exclude` parameters can be used together and the inclusions are applied after the exclusions. +> - __This parameter works only on files.__ +> - Patterns are evaluated using the object's `.Name` property. +> - The `-Include` and `-Exclude` parameters can be used together, however the exclusions are applied before the inclusions. ```yaml Type: String[] @@ -222,7 +228,8 @@ Accept wildcard characters: False ### -Path -Specifies a path to one or more locations. Wildcards are accepted. The default location is the current directory (`.`). +Specifies a path to one or more locations. Wildcards are accepted. +The default location is the current directory (`$PWD`). ```yaml Type: String[] @@ -231,7 +238,7 @@ Aliases: Required: False Position: 0 -Default value: Current directory +Default value: $PWD Accept pipeline input: True (ByValue) Accept wildcard characters: True ``` diff --git a/module/PSTree.psd1 b/module/PSTree.psd1 index c089260..bf6994d 100644 --- a/module/PSTree.psd1 +++ b/module/PSTree.psd1 @@ -11,7 +11,7 @@ RootModule = 'bin/netstandard2.0/PSTree.dll' # Version number of this module. - ModuleVersion = '2.2.0' + ModuleVersion = '2.2.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/src/PSTree/Cache.cs b/src/PSTree/Cache.cs new file mode 100644 index 0000000..b9c584a --- /dev/null +++ b/src/PSTree/Cache.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Linq; +using PSTree.Extensions; + +namespace PSTree; + +internal sealed class Cache +{ + private readonly List _items = []; + + private readonly List _files = []; + + internal void Add(PSTreeFile file) => _files.Add(file); + + internal void Add(PSTreeDirectory directory) => _items.Add(directory); + + internal void Flush() + { + if (_files.Count > 0) + { + _items.AddRange([.. _files]); + _files.Clear(); + } + } + + internal PSTreeFileSystemInfo[] GetTree(bool condition) + { + PSTreeFileSystemInfo[] result = condition + ? [.. _items.Where(static e => e.ShouldInclude)] + : [.. _items]; + + return result.Format(GetItemCount(result)); + } + + private static Dictionary GetItemCount(PSTreeFileSystemInfo[] items) + { + Dictionary counts = []; + foreach (PSTreeFileSystemInfo item in items) + { + string? path = item.ParentNode?.FullName; + if (path is null) + { + continue; + } + + if (!counts.ContainsKey(path)) + { + counts[path] = 0; + } + + counts[path]++; + } + + return counts; + } + + internal void Clear() + { + _files.Clear(); + _items.Clear(); + } +} diff --git a/src/PSTree/CommandWithPathBase.cs b/src/PSTree/CommandWithPathBase.cs new file mode 100644 index 0000000..94c4ca9 --- /dev/null +++ b/src/PSTree/CommandWithPathBase.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Management.Automation; +using PSTree.Extensions; + +namespace PSTree; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class CommandWithPathBase : PSCmdlet +{ + protected const string PathSet = "Path"; + + protected const string LiteralPathSet = "LiteralPath"; + + protected string[]? _paths; + + protected bool IsLiteral + { + get => MyInvocation.BoundParameters.ContainsKey("LiteralPath"); + } + + [Parameter( + ParameterSetName = PathSet, + Position = 0, + ValueFromPipeline = true)] + [SupportsWildcards] + [ValidateNotNullOrEmpty] + public virtual string[]? Path + { + get => _paths; + set => _paths = value; + } + + [Parameter( + ParameterSetName = LiteralPathSet, + ValueFromPipelineByPropertyName = true)] + [Alias("PSPath")] + [ValidateNotNullOrEmpty] + public virtual string[]? LiteralPath + { + get => _paths; + set => _paths = value; + } + + protected IEnumerable EnumerateResolvedPaths() + { + Collection resolvedPaths; + ProviderInfo provider; + + foreach (string path in _paths ?? [SessionState.Path.CurrentLocation.Path]) + { + if (IsLiteral) + { + string resolved = SessionState.Path.GetUnresolvedProviderPathFromPSPath( + path: path, + provider: out provider, + drive: out _); + + if (!provider.IsFileSystem()) + { + WriteError(provider.ToInvalidProviderError(resolved)); + continue; + } + + if (!resolved.Exists()) + { + WriteError(resolved.ToInvalidPathError()); + continue; + } + + yield return resolved; + continue; + } + + try + { + resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider); + } + catch (Exception exception) + { + WriteError(exception.ToResolvePathError(path)); + continue; + } + + foreach (string resolved in resolvedPaths) + { + if (!provider.IsFileSystem()) + { + WriteError(provider.ToInvalidProviderError(resolved)); + continue; + } + + yield return resolved; + } + } + } +} diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 5e0ce2a..86aa937 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -3,62 +3,28 @@ using System.IO; using System.Linq; using System.Management.Automation; +using PSTree.Extensions; namespace PSTree.Commands; -[Cmdlet(VerbsCommon.Get, "PSTree", DefaultParameterSetName = "Path")] +[Cmdlet(VerbsCommon.Get, "PSTree", DefaultParameterSetName = PathSet)] [OutputType(typeof(PSTreeDirectory), typeof(PSTreeFile))] [Alias("pstree")] -public sealed class GetPSTreeCommand : PSCmdlet +public sealed class GetPSTreeCommand : CommandWithPathBase { - private bool _isLiteral; + private bool _withExclude; - private string[]? _paths; + private bool _withInclude; private WildcardPattern[]? _excludePatterns; private WildcardPattern[]? _includePatterns; - private readonly PSTreeIndexer _indexer = new(); - private readonly Stack _stack = new(); - private readonly PSTreeCache _cache = new(); - - private readonly PSTreeComparer _comparer = new(); - - [Parameter( - ParameterSetName = "Path", - Position = 0, - ValueFromPipeline = true - )] - [SupportsWildcards] - [ValidateNotNullOrEmpty] - public string[]? Path - { - get => _paths; - set - { - _paths = value; - _isLiteral = false; - } - } + private readonly Cache _cache = new(); - [Parameter( - ParameterSetName = "LiteralPath", - ValueFromPipelineByPropertyName = true - )] - [Alias("PSPath")] - [ValidateNotNullOrEmpty] - public string[]? LiteralPath - { - get => _paths; - set - { - _paths = value; - _isLiteral = true; - } - } + private readonly TreeComparer _comparer = new(); [Parameter] [ValidateRange(0, int.MaxValue)] @@ -88,126 +54,134 @@ public string[]? LiteralPath protected override void BeginProcessing() { - if (Recurse.IsPresent && !MyInvocation.BoundParameters.ContainsKey("Depth")) + if (Recurse && !MyInvocation.BoundParameters.ContainsKey("Depth")) { Depth = int.MaxValue; } - const WildcardOptions wpoptions = - WildcardOptions.Compiled + const WildcardOptions options = WildcardOptions.Compiled | WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase; if (Exclude is not null) { - _excludePatterns = Exclude - .Select(e => new WildcardPattern(e, wpoptions)) - .ToArray(); + _excludePatterns = [.. Exclude.Select(e => new WildcardPattern(e, options))]; + _withExclude = true; } - // this Parameter only targets files, there is no reason to use it if -Directory is in use - if (Include is not null && !Directory.IsPresent) + if (Include is not null) { - _includePatterns = Include - .Select(e => new WildcardPattern(e, wpoptions)) - .ToArray(); + _includePatterns = [.. Include.Select(e => new WildcardPattern(e, options))]; + _withInclude = true; } } protected override void ProcessRecord() { - _paths ??= [SessionState.Path.CurrentLocation.Path]; - - foreach (string path in _paths.NormalizePath(_isLiteral, this)) + foreach (string path in EnumerateResolvedPaths()) { - string source = path.TrimExcess(); - - if (source.IsArchive()) + if (File.Exists(path)) { - WriteObject(PSTreeFile.Create(new FileInfo(source), source)); + FileInfo file = new(path); + if (!ShouldExclude(file) && ShouldInclude(file)) + { + WriteObject(PSTreeFile.Create(file, path)); + } + continue; } WriteObject( - Traverse(new DirectoryInfo(source), source), + Traverse(PSTreeDirectory.Create(path)), enumerateCollection: true); } } - private PSTreeFileSystemInfo[] Traverse( - DirectoryInfo directory, - string source) + private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) { - _indexer.Clear(); _cache.Clear(); - _stack.Push(PSTreeDirectory.Create(directory, source)); + _stack.Push(directory); + string source = directory.FullName; while (_stack.Count > 0) { PSTreeDirectory next = _stack.Pop(); int level = next.Depth + 1; - long size = 0; - int childCount = 0; + long totalLength = 0; try { bool keepProcessing = level <= Depth; foreach (FileSystemInfo item in next.GetSortedEnumerable(_comparer)) { - childCount++; - if (!Force.IsPresent && item.IsHidden()) + if (!Force && item.IsHidden() || ShouldExclude(item)) { continue; } - if (ShouldExclude(item, _excludePatterns)) + if (item is FileInfo fileInfo) { - continue; - } + if (Directory) + { + totalLength += fileInfo.Length; + continue; + } - if (item is FileInfo file) - { - size += file.Length; + if (!ShouldInclude(fileInfo)) + { + continue; + } - if (Directory.IsPresent) + if (!keepProcessing && !RecursiveSize) { continue; } - if (keepProcessing && ShouldInclude(file, _includePatterns)) + totalLength += fileInfo.Length; + + if (keepProcessing) { - _cache.AddFile(PSTreeFile.Create(file, source, level)); + PSTreeFile file = PSTreeFile + .Create(fileInfo, source, level) + .AddParent(next) + .SetIncludeFlagIf(_withInclude); + + _cache.Add(file); } continue; } - if (keepProcessing || RecursiveSize.IsPresent) + if (!keepProcessing && !RecursiveSize) { - _stack.Push(PSTreeDirectory.Create( - (DirectoryInfo)item, source, level)); + continue; + } + + PSTreeDirectory dir = PSTreeDirectory + .Create((DirectoryInfo)item, source, level) + .AddParent(next); + + if (keepProcessing && Directory || !_withInclude) + { + dir.ShouldInclude = true; } + + _stack.Push(dir); } - next.Length = size; - _indexer[next.FullName] = next; - _indexer.IndexItemCount(next, childCount); + next.Length = totalLength; - if (RecursiveSize.IsPresent) + if (RecursiveSize) { - _indexer.IndexLength(next, size); + next.IndexLength(totalLength); } if (next.Depth <= Depth) { _cache.Add(next); - _cache.TryAddFiles(); + _cache.Flush(); } } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } catch (Exception exception) { if (next.Depth <= Depth) @@ -219,16 +193,14 @@ private PSTreeFileSystemInfo[] Traverse( } } - return _cache.GetTree(); + return _cache.GetTree(_withInclude && !Directory); } - private static bool MatchAny( - FileSystemInfo item, - WildcardPattern[] patterns) + private static bool MatchAny(string name, WildcardPattern[] patterns) { foreach (WildcardPattern pattern in patterns) { - if (pattern.IsMatch(item.FullName)) + if (pattern.IsMatch(name)) { return true; } @@ -237,27 +209,9 @@ private static bool MatchAny( return false; } - private static bool ShouldInclude( - FileInfo file, - WildcardPattern[]? patterns) - { - if (patterns is null) - { - return true; - } - - return MatchAny(file, patterns); - } - - private static bool ShouldExclude( - FileSystemInfo item, - WildcardPattern[]? patterns) - { - if (patterns is null) - { - return false; - } + private bool ShouldInclude(FileInfo item) => + !_withInclude || MatchAny(item.Name, _includePatterns!); - return MatchAny(item, patterns); - } + private bool ShouldExclude(FileSystemInfo item) => + _withExclude && MatchAny(item.Name, _excludePatterns!); } diff --git a/src/PSTree/Exceptions.cs b/src/PSTree/Extensions/ExceptionExtensions.cs similarity index 56% rename from src/PSTree/Exceptions.cs rename to src/PSTree/Extensions/ExceptionExtensions.cs index 6f62473..f38361d 100644 --- a/src/PSTree/Exceptions.cs +++ b/src/PSTree/Extensions/ExceptionExtensions.cs @@ -1,16 +1,20 @@ using System; using System.Management.Automation; -namespace PSTree; +namespace PSTree.Extensions; -internal static class Exceptions +internal static class ExceptionExtensions { internal static ErrorRecord ToInvalidPathError(this string path) => - new(new Exception($"Cannot find path '{path}' because it does not exist."), + new( + new Exception( + $"Cannot find path '{path}' because it does not exist."), "InvalidPath", ErrorCategory.InvalidArgument, path); internal static ErrorRecord ToInvalidProviderError(this ProviderInfo provider, string path) => - new(new ArgumentException($"The resolved path '{path}' is not a FileSystem path but '{provider.Name}'."), + new( + new ArgumentException( + $"The resolved path '{path}' is not a FileSystem path but '{provider.Name}'."), "InvalidProvider", ErrorCategory.InvalidArgument, path); internal static ErrorRecord ToResolvePathError(this Exception exception, string path) => @@ -19,9 +23,11 @@ internal static ErrorRecord ToResolvePathError(this Exception exception, string internal static ErrorRecord ToEnumerationError(this Exception exception, PSTreeFileSystemInfo item) => new(exception, "EnumerationFailure", ErrorCategory.NotSpecified, item); - internal static void ThrowInvalidSequence(this string vt) => throw new ArgumentException( - $"The specified string contains printable content when it should only contain ANSI escape sequences: '{vt}'."); + internal static void ThrowInvalidSequence(this string vt) => + throw new ArgumentException( + $"The specified string contains printable content when it should only contain ANSI escape sequences: '{vt}'."); - internal static void ThrowInvalidExtension(this string extension) => throw new ArgumentException( - $"When adding or removing extensions, the extension must start with a period: '{extension}'."); + internal static void ThrowInvalidExtension(this string extension) => + throw new ArgumentException( + $"When adding or removing extensions, the extension must start with a period: '{extension}'."); } diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs new file mode 100644 index 0000000..2e2b9f6 --- /dev/null +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.Commands; + +namespace PSTree.Extensions; + +internal static class PathExtensions +{ + internal static bool IsFileSystem(this ProviderInfo provider) => + provider.ImplementingType == typeof(FileSystemProvider); + + internal static bool Exists(this string path) => + File.Exists(path) || Directory.Exists(path); + + internal static bool IsHidden(this FileSystemInfo item) => + item.Attributes.HasFlag(FileAttributes.Hidden); +} diff --git a/src/PSTree/Extensions/TreeExtensions.cs b/src/PSTree/Extensions/TreeExtensions.cs new file mode 100644 index 0000000..f09dfee --- /dev/null +++ b/src/PSTree/Extensions/TreeExtensions.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PSTree.Extensions; + +internal static class TreeExtensions +{ + [ThreadStatic] + private static StringBuilder? s_sb; + + internal static string Indent(this string inputString, int indentation) + { + s_sb ??= new StringBuilder(); + s_sb.Clear(); + + return s_sb.Append(' ', (4 * indentation) - 4) + .Append("└── ") + .Append(inputString) + .ToString(); + } + + internal static PSTreeFileSystemInfo[] Format( + this PSTreeFileSystemInfo[] tree, + Dictionary itemCounts) + { + int index; + for (int i = 0; i < tree.Length; i++) + { + PSTreeFileSystemInfo current = tree[i]; + + if (current is PSTreeDirectory directory && + itemCounts.TryGetValue(directory.FullName, out int count)) + { + directory.IndexCount(count); + } + + if ((index = current.Hierarchy.IndexOf('└')) == -1) + { + continue; + } + + for (int z = i - 1; z >= 0; z--) + { + current = tree[z]; + string hierarchy = current.Hierarchy; + + if (char.IsWhiteSpace(hierarchy[index])) + { + current.Hierarchy = hierarchy.ReplaceAt(index, '│'); + continue; + } + + if (hierarchy[index] == '└') + { + current.Hierarchy = hierarchy.ReplaceAt(index, '├'); + } + + break; + } + } + + return tree; + } + + private static string ReplaceAt(this string input, int index, char newChar) + { + char[] chars = input.ToCharArray(); + chars[index] = newChar; + return new string(chars); + } +} diff --git a/src/PSTree/PSTreeCache.cs b/src/PSTree/PSTreeCache.cs deleted file mode 100644 index 0752db1..0000000 --- a/src/PSTree/PSTreeCache.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; - -namespace PSTree; - -internal sealed class PSTreeCache -{ - private readonly List _items = []; - - private readonly List _files = []; - - internal void AddFile(PSTreeFile file) => _files.Add(file); - - internal void Add(PSTreeFileSystemInfo item) => _items.Add(item); - - internal void TryAddFiles() - { - if (_files.Count > 0) - { - _items.AddRange([.. _files]); - _files.Clear(); - } - } - - internal PSTreeFileSystemInfo[] GetTree() => _items.ToArray().ConvertToTree(); - - internal void Clear() - { - _files.Clear(); - _items.Clear(); - } -} diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index 41737b0..9bac7ea 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -1,16 +1,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using PSTree.Extensions; using PSTree.Style; namespace PSTree; public sealed class PSTreeDirectory : PSTreeFileSystemInfo { - private string[]? _parents; - - internal string[] Parents { get => _parents ??= GetParents(FullName); } - public DirectoryInfo Parent => Instance.Parent; public int ItemCount { get; internal set; } @@ -25,7 +22,9 @@ private PSTreeDirectory( private PSTreeDirectory( DirectoryInfo dir, string hierarchy, string source) : base(dir, hierarchy, source) - { } + { + ShouldInclude = true; + } public IEnumerable EnumerateFiles() => Instance.EnumerateFiles(); @@ -36,25 +35,15 @@ public IEnumerable EnumerateDirectories() => public IEnumerable EnumerateFileSystemInfos() => Instance.EnumerateFileSystemInfos(); - private static string[] GetParents(string path) - { - int index = -1; - List parents = []; - - while ((index = path.IndexOf(Path.DirectorySeparatorChar, index + 1)) != -1) - { - parents.Add(path.Substring(0, index)); - } - - return [.. parents]; - } - - internal IOrderedEnumerable GetSortedEnumerable(PSTreeComparer comparer) => + internal IOrderedEnumerable GetSortedEnumerable(TreeComparer comparer) => Instance .EnumerateFileSystemInfos() .OrderBy(static e => e is DirectoryInfo) .ThenBy(static e => e, comparer); + internal static PSTreeDirectory Create(string path) => + Create(new DirectoryInfo(path), path); + internal static PSTreeDirectory Create(DirectoryInfo dir, string source) { string styled = TreeStyle.Instance.GetColoredName(dir); @@ -66,4 +55,44 @@ internal static PSTreeDirectory Create(DirectoryInfo dir, string source, int dep string styled = TreeStyle.Instance.GetColoredName(dir).Indent(depth); return new PSTreeDirectory(dir, styled, source, depth); } + + internal PSTreeDirectory AddParent(PSTreeDirectory parent) + { + ParentNode = parent; + return this; + } + + internal void IndexCount(int count) + { + ItemCount = count; + TotalItemCount = count; + + for (PSTreeDirectory? i = ParentNode; i is not null; i = i.ParentNode) + { + i.TotalItemCount += count; + } + } + + internal void IndexLength(long length) + { + for (PSTreeDirectory? i = ParentNode; i is not null; i = i.ParentNode) + { + i.Length += length; + } + } + + internal void SetIncludeFlag() + { + ShouldInclude = true; + + for (PSTreeDirectory? i = ParentNode; i is not null; i = i.ParentNode) + { + if (i.ShouldInclude) + { + return; + } + + i.ShouldInclude = true; + } + } } diff --git a/src/PSTree/PSTreeExtensions.cs b/src/PSTree/PSTreeExtensions.cs deleted file mode 100644 index 477b045..0000000 --- a/src/PSTree/PSTreeExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Text; - -namespace PSTree; - -internal static class PSTreeExtensions -{ - private static readonly StringBuilder s_sb = new(); - - internal static string Indent(this string inputString, int indentation) - { - s_sb.Clear(); - - return s_sb.Append(' ', (4 * indentation) - 4) - .Append("└── ") - .Append(inputString) - .ToString(); - } - - internal static PSTreeFileSystemInfo[] ConvertToTree( - this PSTreeFileSystemInfo[] inputObject) - { - int index; - PSTreeFileSystemInfo current; - for (int i = 0; i < inputObject.Length; i++) - { - current = inputObject[i]; - if ((index = current.Hierarchy.IndexOf('└')) == -1) - { - continue; - } - - int z; - char[] replace; - for (z = i - 1; z >= 0; z--) - { - current = inputObject[z]; - if (!char.IsWhiteSpace(current.Hierarchy[index])) - { - UpdateCorner(index, current); - break; - } - - replace = current.Hierarchy.ToCharArray(); - replace[index] = '│'; - current.Hierarchy = new string(replace); - } - } - - return inputObject; - } - - private static void UpdateCorner(int index, PSTreeFileSystemInfo current) - { - if (current.Hierarchy[index] == '└') - { - char[] replace = current.Hierarchy.ToCharArray(); - replace[index] = '├'; - current.Hierarchy = new string(replace); - } - } -} diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index d2b0187..8a8e289 100644 --- a/src/PSTree/PSTreeFile.cs +++ b/src/PSTree/PSTreeFile.cs @@ -1,4 +1,5 @@ using System.IO; +using PSTree.Extensions; using PSTree.Style; namespace PSTree; @@ -10,14 +11,20 @@ public sealed class PSTreeFile : PSTreeFileSystemInfo public string DirectoryName => Instance.DirectoryName; private PSTreeFile( - FileInfo file, string hierarchy, string source, int depth) - : base(file, hierarchy, source, depth) => + FileInfo file, string hierarchy, string source) + : base(file, hierarchy, source) + { Length = file.Length; + ShouldInclude = true; + } private PSTreeFile( - FileInfo file, string hierarchy, string source) - : base(file, hierarchy, source) => + FileInfo file, string hierarchy, string source, int depth) + : base(file, hierarchy, source, depth) + { Length = file.Length; + ShouldInclude = true; + } internal static PSTreeFile Create(FileInfo file, string source) { @@ -30,4 +37,20 @@ internal static PSTreeFile Create(FileInfo file, string source, int depth) string styled = TreeStyle.Instance.GetColoredName(file).Indent(depth); return new PSTreeFile(file, styled, source, depth); } + + internal PSTreeFile AddParent(PSTreeDirectory parent) + { + ParentNode = parent; + return this; + } + + internal PSTreeFile SetIncludeFlagIf(bool condition) + { + if (condition) + { + ParentNode?.SetIncludeFlag(); + } + + return this; + } } diff --git a/src/PSTree/PSTreeFileSystemInfo.cs b/src/PSTree/PSTreeFileSystemInfo.cs index e279a44..9c36ab8 100644 --- a/src/PSTree/PSTreeFileSystemInfo.cs +++ b/src/PSTree/PSTreeFileSystemInfo.cs @@ -2,6 +2,10 @@ namespace PSTree; public abstract class PSTreeFileSystemInfo(string hierarchy, string source) { + internal PSTreeDirectory? ParentNode { get; set; } + + internal bool ShouldInclude { get; set; } + internal string Source { get; set; } = source; public int Depth { get; protected set; } diff --git a/src/PSTree/PSTreeIndexer.cs b/src/PSTree/PSTreeIndexer.cs deleted file mode 100644 index 035e21d..0000000 --- a/src/PSTree/PSTreeIndexer.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace PSTree; - -internal sealed class PSTreeIndexer -{ - private readonly Dictionary _indexer = []; - - internal PSTreeDirectory this[string path] - { - set => _indexer[path.TrimEnd(Path.DirectorySeparatorChar)] = value; - } - - internal void IndexLength(PSTreeDirectory directory, long length) - { - foreach (string parent in directory.Parents) - { - if (_indexer.TryGetValue(parent, out PSTreeDirectory paretDir)) - { - paretDir.Length += length; - } - } - } - - internal void IndexItemCount(PSTreeDirectory directory, int count) - { - directory.ItemCount = count; - directory.TotalItemCount = count; - - foreach (string parent in directory.Parents) - { - if (_indexer.TryGetValue(parent, out PSTreeDirectory parentDir)) - { - parentDir.TotalItemCount += count; - } - } - } - - internal void Clear() => _indexer.Clear(); -} diff --git a/src/PSTree/PathExtensions.cs b/src/PSTree/PathExtensions.cs deleted file mode 100644 index 6435765..0000000 --- a/src/PSTree/PathExtensions.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Management.Automation; -using Microsoft.PowerShell.Commands; - -namespace PSTree; - -internal static class PathExtensions -{ - [ThreadStatic] - private static List? s_normalizedPaths; - - private static readonly char[] _dirSeparator = @"\/".ToCharArray(); - - internal static string[] NormalizePath( - this string[] paths, - bool isLiteral, - PSCmdlet cmdlet) - { - s_normalizedPaths ??= []; - s_normalizedPaths.Clear(); - - Collection resolvedPaths; - ProviderInfo provider; - - foreach (string path in paths) - { - if (isLiteral) - { - string resolvedPath = cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath( - path, out provider, out _); - - if (!provider.IsFileSystem()) - { - cmdlet.WriteError(provider.ToInvalidProviderError(path)); - continue; - } - - if (!resolvedPath.Exists()) - { - cmdlet.WriteError(resolvedPath.ToInvalidPathError()); - continue; - } - - s_normalizedPaths.Add(resolvedPath); - continue; - } - - try - { - resolvedPaths = cmdlet.GetResolvedProviderPathFromPSPath(path, out provider); - - foreach (string resolvedPath in resolvedPaths) - { - if (!provider.IsFileSystem()) - { - cmdlet.WriteError(provider.ToInvalidProviderError(resolvedPath)); - continue; - } - - s_normalizedPaths.Add(resolvedPath); - } - } - catch (Exception exception) - { - cmdlet.WriteError(exception.ToResolvePathError(path)); - } - } - - return [.. s_normalizedPaths]; - } - - internal static string NormalizePath(this string path, bool isLiteral, PSCmdlet cmdlet) => - NormalizePath([path], isLiteral, cmdlet) - .FirstOrDefault(); - - internal static bool IsFileSystem(this ProviderInfo provider) => - provider.ImplementingType == typeof(FileSystemProvider); - - internal static bool IsArchive(this string path) => - !File.GetAttributes(path).HasFlag(FileAttributes.Directory); - - internal static bool Exists(this string path) => - File.Exists(path) || Directory.Exists(path); - - internal static bool IsHidden(this FileSystemInfo item) => - item.Attributes.HasFlag(FileAttributes.Hidden); - - internal static string TrimExcess(this string path) - { - path = path.TrimEnd(_dirSeparator); - - if (string.IsNullOrWhiteSpace(path)) - { - return Path.DirectorySeparatorChar.ToString(); - } - - if (Path.GetPathRoot(path) == path) - { - return string.Concat(path.ToUpper(), Path.DirectorySeparatorChar); - } - - return path; - } -} diff --git a/src/PSTree/Style/Extension.cs b/src/PSTree/Style/Extension.cs index 3c43b48..0bd2635 100644 --- a/src/PSTree/Style/Extension.cs +++ b/src/PSTree/Style/Extension.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Management.Automation; using System.Text; +using PSTree.Extensions; namespace PSTree.Style; @@ -47,7 +48,7 @@ internal Extension() public override string ToString() { StringBuilder builder = new(_extension.Count); - int len = _extension.Keys.Max(e => e.Length); + int len = _extension.Keys.Max(static e => e.Length); foreach (KeyValuePair pair in _extension) { builder diff --git a/src/PSTree/Style/TreeStyle.cs b/src/PSTree/Style/TreeStyle.cs index b96e4dd..211caef 100644 --- a/src/PSTree/Style/TreeStyle.cs +++ b/src/PSTree/Style/TreeStyle.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; +using PSTree.Extensions; namespace PSTree.Style; diff --git a/src/PSTree/PSTreeComparer.cs b/src/PSTree/TreeComparer.cs similarity index 83% rename from src/PSTree/PSTreeComparer.cs rename to src/PSTree/TreeComparer.cs index e08865e..e07985c 100644 --- a/src/PSTree/PSTreeComparer.cs +++ b/src/PSTree/TreeComparer.cs @@ -3,7 +3,7 @@ namespace PSTree; -internal sealed class PSTreeComparer : IComparer +internal sealed class TreeComparer : IComparer { public int Compare(FileSystemInfo x, FileSystemInfo y) => x is DirectoryInfo && y is DirectoryInfo diff --git a/tests/GetPSTreeCommand.tests.ps1 b/tests/GetPSTreeCommand.tests.ps1 index 27d69af..aa838dd 100644 --- a/tests/GetPSTreeCommand.tests.ps1 +++ b/tests/GetPSTreeCommand.tests.ps1 @@ -71,16 +71,12 @@ Describe 'Get-PSTree' { Should -Not -BeNullOrEmpty } - It 'Should trim excess directory path separators' { - (Get-PSTree /\/\/\/\ -Depth 0).FullName | Should -BeExactly (Get-Item /\).FullName - } - It '-Path should support wildcards' { Get-PSTree * | Should -Not -BeNullOrEmpty } It 'Can take pipeline input to -Path' { - (Get-ChildItem $testPath).FullName | Get-PSTree | + (Get-ChildItem $testPath).FullName | Get-PSTree | Should -Not -BeNullOrEmpty } @@ -109,7 +105,7 @@ Describe 'Get-PSTree' { $tree.FullName | Should -Not -Contain $ref } - It 'Excludes childs with -Exclude parameter' { + It 'Excludes child items with -Exclude parameter' { $exclude = '*tools*', '*build*', '*.ps1' Get-PSTree $testPath -Exclude * | Should -HaveCount 1 Get-PSTree $testPath -Exclude $exclude -Recurse | ForEach-Object { @@ -117,9 +113,13 @@ Describe 'Get-PSTree' { [string[]] $exclude, [System.Func[string, bool]] { $_.FullName -like $args[0] }) } | Should -Not -BeTrue + + Get-ChildItem $testPath -Filter *.ps1 -Recurse | + Get-PSTree -Exclude *.ps1 | + Should -BeNullOrEmpty } - It 'Include childs with -Include parameter' { + It 'Includes child items with -Include parameter' { $include = '*.ps1', '*.cs' Get-PSTree $testPath -Include $include -Recurse | ForEach-Object { [System.Linq.Enumerable]::Any( @@ -129,6 +129,10 @@ Describe 'Get-PSTree' { } ) } | Should -BeTrue + + Get-ChildItem $testPath -Filter *.ps1 -Recurse | + Get-PSTree -Include *.ps1 | + Should -Not -BeNullOrEmpty } It 'Should prioritize -Depth if used together with -Recurse' { @@ -138,7 +142,7 @@ Describe 'Get-PSTree' { Should -Not -Contain $ref } - It 'Calculates the recursive size of folders with -RecrusiveSize' { + It 'Calculates the recursive size of folders with -RecursiveSize' { $recursiveLengths = Join-Path $testPath src | Get-PSTree -Directory -Depth 1 -RecursiveSize @@ -153,6 +157,15 @@ Describe 'Get-PSTree' { } } + It '-RecursiveSize and -Include can work together' { + $measure = Get-ChildItem -Recurse -Include *.cs, *.ps1 | + Measure-Object Length -Sum + + Get-PSTree -Include *.cs, *.ps1 -RecursiveSize -Depth 0 | + ForEach-Object Length | + Should -BeExactly $measure.Sum + } + It 'Should traverse all tree when -Recurse is used' { (Get-PSTree $testPath -Recurse | Select-Object -Skip 1 | Measure-Object).Count | Should -BeExactly (Get-ChildItem $testPath -Recurse | Measure-Object).Count diff --git a/tests/PSTreeDirectory.tests.ps1 b/tests/PSTreeDirectory.tests.ps1 index c3abe37..af8c0ef 100644 --- a/tests/PSTreeDirectory.tests.ps1 +++ b/tests/PSTreeDirectory.tests.ps1 @@ -27,13 +27,16 @@ Describe 'PSTreeDirectory' { (Get-PSTree $testPath -Depth 0).Parent | Should -BeOfType ([System.IO.DirectoryInfo]) } - It 'ItemCount gets the count of direct childs' { + It 'ItemCount gets the count of direct child items' { $childCount = @(Get-ChildItem -Force $testPath).Count (Get-PSTree $testPath -Depth 1 -Force)[0].ItemCount | Should -BeExactly $childCount } - It 'TotalItemCount gets the recursive count of childs' { + It 'TotalItemCount gets the recursive count of child items' { $childCount = @(Get-ChildItem -Force $testPath -Recurse).Count (Get-PSTree $testPath -Recurse -Force)[0].TotalItemCount | Should -BeExactly $childCount + + $childCount = @(Get-ChildItem $testPath -Depth 2).Count + (Get-PSTree $testPath)[0].TotalItemCount | Should -BeExactly $childCount } } diff --git a/tests/PathExtensions.tests.ps1 b/tests/PathExtensions.tests.ps1 deleted file mode 100644 index 07b2136..0000000 --- a/tests/PathExtensions.tests.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -$ErrorActionPreference = 'Stop' - -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) - -Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) - - -Describe 'PathExtensions' { - BeforeAll { - $normalizePath = (Get-PSTree -Depth 0).GetType(). - Assembly. - GetType('PSTree.PathExtensions'). - GetMethod( - 'NormalizePath', - [System.Reflection.BindingFlags] 'NonPublic, Static', - $null, - [type[]] ([string], [bool], [System.Management.Automation.PSCmdlet], [bool], [bool]), - $null) - - $normalizePath | Out-Null - } - - It 'Should throw on Provider Paths' { - { $normalizePath | Test-NormalizePath function: -IsLiteral } | - Should -Throw - - { $normalizePath | Test-NormalizePath function: } | - Should -Throw - - { $normalizePath | Test-NormalizePath function: -ThrowOnInvalidProvider } | - Should -Throw - } - - It 'Should throw on invalid Paths' { - { $normalizePath | Test-NormalizePath \does\not\exist -IsLiteral } | - Should -Throw - - { $normalizePath | Test-NormalizePath \does\not\exist } | - Should -Throw - - { $normalizePath | Test-NormalizePath \does\not\exist -ThrowOnInvalidPath } | - Should -Throw - } -} diff --git a/tools/requiredModules.psd1 b/tools/requiredModules.psd1 index a486014..46460f7 100644 --- a/tools/requiredModules.psd1 +++ b/tools/requiredModules.psd1 @@ -1,6 +1,6 @@ @{ - InvokeBuild = '5.11.2' + InvokeBuild = '5.12.1' platyPS = '0.14.2' - PSScriptAnalyzer = '1.22.0' - Pester = '5.6.0' + PSScriptAnalyzer = '1.23.0' + Pester = '5.7.1' }