From 2364d4b7ddbef69031a23c1f3b577e366f464317 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Wed, 15 Jan 2025 14:31:20 -0300 Subject: [PATCH 01/19] a few code improvements --- src/PSTree/CommandWithPathBase.cs | 89 +++++++++++++++ src/PSTree/Commands/GetPSTreeCommand.cs | 106 ++--------------- src/PSTree/{ => Extensions}/Exceptions.cs | 22 ++-- .../{ => Extensions}/PSTreeExtensions.cs | 4 +- src/PSTree/Extensions/PathExtensions.cs | 87 ++++++++++++++ src/PSTree/PSTreeCache.cs | 1 + src/PSTree/PSTreeDirectory.cs | 1 + src/PSTree/PSTreeFile.cs | 9 +- src/PSTree/PathExtensions.cs | 108 ------------------ src/PSTree/Style/Extension.cs | 1 + src/PSTree/Style/TreeStyle.cs | 1 + tests/PathExtensions.tests.ps1 | 46 -------- 12 files changed, 213 insertions(+), 262 deletions(-) create mode 100644 src/PSTree/CommandWithPathBase.cs rename src/PSTree/{ => Extensions}/Exceptions.cs (56%) rename src/PSTree/{ => Extensions}/PSTreeExtensions.cs (96%) create mode 100644 src/PSTree/Extensions/PathExtensions.cs delete mode 100644 src/PSTree/PathExtensions.cs delete mode 100644 tests/PathExtensions.tests.ps1 diff --git a/src/PSTree/CommandWithPathBase.cs b/src/PSTree/CommandWithPathBase.cs new file mode 100644 index 0000000..8621261 --- /dev/null +++ b/src/PSTree/CommandWithPathBase.cs @@ -0,0 +1,89 @@ +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 resolvedPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath( + path: path, + provider: out provider, + drive: out _); + + if (provider.Validate(resolvedPath, this)) + { + yield return resolvedPath; + } + + continue; + } + + try + { + resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider); + } + catch (Exception exception) + { + WriteError(exception.ToResolvePathError(path)); + continue; + } + + + foreach (string resolvedPath in resolvedPaths) + { + if (provider.Validate(resolvedPath, this)) + { + yield return resolvedPath; + } + } + } + } +} diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 5e0ce2a..8309966 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -3,18 +3,15 @@ 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 string[]? _paths; - private WildcardPattern[]? _excludePatterns; private WildcardPattern[]? _includePatterns; @@ -27,39 +24,6 @@ public sealed class GetPSTreeCommand : PSCmdlet private readonly PSTreeComparer _comparer = new(); - [Parameter( - ParameterSetName = "Path", - Position = 0, - ValueFromPipeline = true - )] - [SupportsWildcards] - [ValidateNotNullOrEmpty] - public string[]? Path - { - get => _paths; - set - { - _paths = value; - _isLiteral = false; - } - } - - [Parameter( - ParameterSetName = "LiteralPath", - ValueFromPipelineByPropertyName = true - )] - [Alias("PSPath")] - [ValidateNotNullOrEmpty] - public string[]? LiteralPath - { - get => _paths; - set - { - _paths = value; - _isLiteral = true; - } - } - [Parameter] [ValidateRange(0, int.MaxValue)] public int Depth { get; set; } = 3; @@ -93,36 +57,29 @@ protected override void BeginProcessing() 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))]; } // this Parameter only targets files, there is no reason to use it if -Directory is in use if (Include is not null && !Directory.IsPresent) { - _includePatterns = Include - .Select(e => new WildcardPattern(e, wpoptions)) - .ToArray(); + _includePatterns = [.. Include.Select(e => new WildcardPattern(e, options))]; } } 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(source)) { WriteObject(PSTreeFile.Create(new FileInfo(source), source)); continue; @@ -160,7 +117,7 @@ private PSTreeFileSystemInfo[] Traverse( continue; } - if (ShouldExclude(item, _excludePatterns)) + if (item.ShouldExclude(_excludePatterns)) { continue; } @@ -174,7 +131,7 @@ private PSTreeFileSystemInfo[] Traverse( continue; } - if (keepProcessing && ShouldInclude(file, _includePatterns)) + if (keepProcessing && file.ShouldInclude(_includePatterns)) { _cache.AddFile(PSTreeFile.Create(file, source, level)); } @@ -204,10 +161,6 @@ private PSTreeFileSystemInfo[] Traverse( _cache.TryAddFiles(); } } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } catch (Exception exception) { if (next.Depth <= Depth) @@ -221,43 +174,4 @@ private PSTreeFileSystemInfo[] Traverse( return _cache.GetTree(); } - - private static bool MatchAny( - FileSystemInfo item, - WildcardPattern[] patterns) - { - foreach (WildcardPattern pattern in patterns) - { - if (pattern.IsMatch(item.FullName)) - { - return true; - } - } - - 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; - } - - return MatchAny(item, patterns); - } } diff --git a/src/PSTree/Exceptions.cs b/src/PSTree/Extensions/Exceptions.cs similarity index 56% rename from src/PSTree/Exceptions.cs rename to src/PSTree/Extensions/Exceptions.cs index 6f62473..f38361d 100644 --- a/src/PSTree/Exceptions.cs +++ b/src/PSTree/Extensions/Exceptions.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/PSTreeExtensions.cs b/src/PSTree/Extensions/PSTreeExtensions.cs similarity index 96% rename from src/PSTree/PSTreeExtensions.cs rename to src/PSTree/Extensions/PSTreeExtensions.cs index 477b045..d2cce5c 100644 --- a/src/PSTree/PSTreeExtensions.cs +++ b/src/PSTree/Extensions/PSTreeExtensions.cs @@ -1,8 +1,8 @@ using System.Text; -namespace PSTree; +namespace PSTree.Extensions; -internal static class PSTreeExtensions +internal static class TreeExtensions { private static readonly StringBuilder s_sb = new(); diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs new file mode 100644 index 0000000..2c63678 --- /dev/null +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -0,0 +1,87 @@ +using System.IO; +using System.Management.Automation; +using Microsoft.PowerShell.Commands; + +namespace PSTree.Extensions; + +internal static class PathExtensions +{ + private static readonly char[] _dirSeparator = @"\/".ToCharArray(); + + internal static bool Validate( + this ProviderInfo provider, + string path, + PSCmdlet cmdlet) + { + if (provider.ImplementingType == typeof(FileSystemProvider)) + { + return true; + } + + ErrorRecord error = provider.ToInvalidProviderError(path); + cmdlet.WriteError(error); + return false; + } + + 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 System.IO.Path.DirectorySeparatorChar.ToString(); + } + + if (System.IO.Path.GetPathRoot(path) == path) + { + return string.Concat(path.ToUpper(), System.IO.Path.DirectorySeparatorChar); + } + + return path; + } + + private static bool MatchAny( + FileSystemInfo item, + WildcardPattern[] patterns) + { + foreach (WildcardPattern pattern in patterns) + { + if (pattern.IsMatch(item.FullName)) + { + return true; + } + } + + return false; + } + + internal static bool ShouldInclude( + this FileInfo file, + WildcardPattern[]? patterns) + { + if (patterns is null) + { + return true; + } + + return MatchAny(file, patterns); + } + + internal static bool ShouldExclude( + this FileSystemInfo item, + WildcardPattern[]? patterns) + { + if (patterns is null) + { + return false; + } + + return MatchAny(item, patterns); + } +} diff --git a/src/PSTree/PSTreeCache.cs b/src/PSTree/PSTreeCache.cs index 0752db1..9d3d40c 100644 --- a/src/PSTree/PSTreeCache.cs +++ b/src/PSTree/PSTreeCache.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using PSTree.Extensions; namespace PSTree; diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index 41737b0..f66b4da 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using PSTree.Extensions; using PSTree.Style; namespace PSTree; diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index d2b0187..b2a529c 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; @@ -11,13 +12,17 @@ public sealed class PSTreeFile : PSTreeFileSystemInfo private PSTreeFile( FileInfo file, string hierarchy, string source, int depth) - : base(file, hierarchy, source, depth) => + : base(file, hierarchy, source, depth) + { Length = file.Length; + } private PSTreeFile( FileInfo file, string hierarchy, string source) - : base(file, hierarchy, source) => + : base(file, hierarchy, source) + { Length = file.Length; + } internal static PSTreeFile Create(FileInfo file, string source) { 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..8adf5e9 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; 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/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 - } -} From 9a503b8cbd5abcbd60e0233707ea6f8978465f03 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 13:19:40 -0300 Subject: [PATCH 02/19] code improvements. simplifies logic. --- src/PSTree/Commands/GetPSTreeCommand.cs | 31 ++++++------- .../{Exceptions.cs => ExceptionExtensions.cs} | 0 src/PSTree/Extensions/PathExtensions.cs | 19 -------- ...{PSTreeExtensions.cs => TreeExtensions.cs} | 0 src/PSTree/PSTreeDirectory.cs | 44 ++++++++++++------- src/PSTree/PSTreeFile.cs | 2 + src/PSTree/PSTreeIndexer.cs | 41 ----------------- tools/requiredModules.psd1 | 6 +-- 8 files changed, 47 insertions(+), 96 deletions(-) rename src/PSTree/Extensions/{Exceptions.cs => ExceptionExtensions.cs} (100%) rename src/PSTree/Extensions/{PSTreeExtensions.cs => TreeExtensions.cs} (100%) delete mode 100644 src/PSTree/PSTreeIndexer.cs diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 8309966..4d3e8ea 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -16,8 +16,6 @@ public sealed class GetPSTreeCommand : CommandWithPathBase private WildcardPattern[]? _includePatterns; - private readonly PSTreeIndexer _indexer = new(); - private readonly Stack _stack = new(); private readonly PSTreeCache _cache = new(); @@ -77,27 +75,23 @@ protected override void ProcessRecord() { foreach (string path in EnumerateResolvedPaths()) { - string source = path.TrimExcess(); - - if (File.Exists(source)) + if (File.Exists(path)) { - WriteObject(PSTreeFile.Create(new FileInfo(source), source)); + WriteObject(PSTreeFile.Create(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) { @@ -112,6 +106,7 @@ private PSTreeFileSystemInfo[] Traverse( foreach (FileSystemInfo item in next.GetSortedEnumerable(_comparer)) { childCount++; + if (!Force.IsPresent && item.IsHidden()) { continue; @@ -141,18 +136,20 @@ private PSTreeFileSystemInfo[] Traverse( if (keepProcessing || RecursiveSize.IsPresent) { - _stack.Push(PSTreeDirectory.Create( - (DirectoryInfo)item, source, level)); + PSTreeDirectory dir = PSTreeDirectory + .Create((DirectoryInfo)item, source, level) + .WithParent(next); + + _stack.Push(dir); } } next.Length = size; - _indexer[next.FullName] = next; - _indexer.IndexItemCount(next, childCount); + next.IndexItemCount(childCount); if (RecursiveSize.IsPresent) { - _indexer.IndexLength(next, size); + next.IndexLength(size); } if (next.Depth <= Depth) diff --git a/src/PSTree/Extensions/Exceptions.cs b/src/PSTree/Extensions/ExceptionExtensions.cs similarity index 100% rename from src/PSTree/Extensions/Exceptions.cs rename to src/PSTree/Extensions/ExceptionExtensions.cs diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs index 2c63678..4b2f651 100644 --- a/src/PSTree/Extensions/PathExtensions.cs +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -6,8 +6,6 @@ namespace PSTree.Extensions; internal static class PathExtensions { - private static readonly char[] _dirSeparator = @"\/".ToCharArray(); - internal static bool Validate( this ProviderInfo provider, string path, @@ -29,23 +27,6 @@ internal static bool Exists(this string 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 System.IO.Path.DirectorySeparatorChar.ToString(); - } - - if (System.IO.Path.GetPathRoot(path) == path) - { - return string.Concat(path.ToUpper(), System.IO.Path.DirectorySeparatorChar); - } - - return path; - } - private static bool MatchAny( FileSystemInfo item, WildcardPattern[] patterns) diff --git a/src/PSTree/Extensions/PSTreeExtensions.cs b/src/PSTree/Extensions/TreeExtensions.cs similarity index 100% rename from src/PSTree/Extensions/PSTreeExtensions.cs rename to src/PSTree/Extensions/TreeExtensions.cs diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index f66b4da..7a252b0 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -8,9 +8,7 @@ namespace PSTree; public sealed class PSTreeDirectory : PSTreeFileSystemInfo { - private string[]? _parents; - - internal string[] Parents { get => _parents ??= GetParents(FullName); } + private PSTreeDirectory? _parent; public DirectoryInfo Parent => Instance.Parent; @@ -37,25 +35,14 @@ 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) => 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); @@ -67,4 +54,29 @@ 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 WithParent(PSTreeDirectory parent) + { + _parent = parent; + return this; + } + + internal void IndexItemCount(int count) + { + ItemCount = count; + TotalItemCount = count; + + for (PSTreeDirectory? parent = _parent; parent is not null; parent = parent._parent) + { + parent.TotalItemCount += count; + } + } + + internal void IndexLength(long length) + { + for (PSTreeDirectory? parent = _parent; parent is not null; parent = parent._parent) + { + parent.Length += length; + } + } } diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index b2a529c..cb81da0 100644 --- a/src/PSTree/PSTreeFile.cs +++ b/src/PSTree/PSTreeFile.cs @@ -24,6 +24,8 @@ private PSTreeFile( Length = file.Length; } + internal static PSTreeFile Create(string path) => Create(new FileInfo(path), path); + internal static PSTreeFile Create(FileInfo file, string source) { string styled = TreeStyle.Instance.GetColoredName(file); 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/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' } From 1620d1e1e42d39bdd9ef6eaec2217df86e0faee7 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 13:23:03 -0300 Subject: [PATCH 03/19] code improvements. simplifies logic. --- src/PSTree/{PSTreeCache.cs => Cache.cs} | 2 +- src/PSTree/Commands/GetPSTreeCommand.cs | 4 ++-- src/PSTree/PSTreeDirectory.cs | 2 +- src/PSTree/{PSTreeComparer.cs => TreeComparer.cs} | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/PSTree/{PSTreeCache.cs => Cache.cs} (95%) rename src/PSTree/{PSTreeComparer.cs => TreeComparer.cs} (83%) diff --git a/src/PSTree/PSTreeCache.cs b/src/PSTree/Cache.cs similarity index 95% rename from src/PSTree/PSTreeCache.cs rename to src/PSTree/Cache.cs index 9d3d40c..5427d8f 100644 --- a/src/PSTree/PSTreeCache.cs +++ b/src/PSTree/Cache.cs @@ -3,7 +3,7 @@ namespace PSTree; -internal sealed class PSTreeCache +internal sealed class Cache { private readonly List _items = []; diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 4d3e8ea..3232254 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -18,9 +18,9 @@ public sealed class GetPSTreeCommand : CommandWithPathBase private readonly Stack _stack = new(); - private readonly PSTreeCache _cache = new(); + private readonly Cache _cache = new(); - private readonly PSTreeComparer _comparer = new(); + private readonly TreeComparer _comparer = new(); [Parameter] [ValidateRange(0, int.MaxValue)] diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index 7a252b0..bcf909e 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -35,7 +35,7 @@ public IEnumerable EnumerateDirectories() => public IEnumerable EnumerateFileSystemInfos() => Instance.EnumerateFileSystemInfos(); - internal IOrderedEnumerable GetSortedEnumerable(PSTreeComparer comparer) => + internal IOrderedEnumerable GetSortedEnumerable(TreeComparer comparer) => Instance .EnumerateFileSystemInfos() .OrderBy(static e => e is DirectoryInfo) 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 From c40d45e429b83b421db2e2fccf9061dbee2068ea Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 14:47:08 -0300 Subject: [PATCH 04/19] another try --- src/PSTree/CommandWithPathBase.cs | 28 +++++++++++------ src/PSTree/Extensions/PathExtensions.cs | 40 ++++++++++++++++++++----- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/PSTree/CommandWithPathBase.cs b/src/PSTree/CommandWithPathBase.cs index 8621261..f6ba882 100644 --- a/src/PSTree/CommandWithPathBase.cs +++ b/src/PSTree/CommandWithPathBase.cs @@ -53,22 +53,30 @@ protected IEnumerable EnumerateResolvedPaths() { if (IsLiteral) { - string resolvedPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath( - path: path, + string resolved = SessionState.Path.GetUnresolvedProviderPathFromPSPath( + path: path.Normalize(), provider: out provider, drive: out _); - if (provider.Validate(resolvedPath, this)) + if (!provider.ValidateProvider()) { - yield return resolvedPath; + WriteError(provider.ToInvalidProviderError(resolved)); + continue; } + if (!resolved.ValidateExists()) + { + WriteError(resolved.ToInvalidPathError()); + continue; + } + + yield return resolved; continue; } try { - resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider); + resolvedPaths = GetResolvedProviderPathFromPSPath(path.Normalize(), out provider); } catch (Exception exception) { @@ -76,13 +84,15 @@ protected IEnumerable EnumerateResolvedPaths() continue; } - - foreach (string resolvedPath in resolvedPaths) + foreach (string resolved in resolvedPaths) { - if (provider.Validate(resolvedPath, this)) + if (!provider.ValidateProvider()) { - yield return resolvedPath; + WriteError(provider.ToInvalidProviderError(resolved)); + continue; } + + yield return resolved; } } } diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs index 4b2f651..05c2146 100644 --- a/src/PSTree/Extensions/PathExtensions.cs +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -6,23 +6,47 @@ namespace PSTree.Extensions; internal static class PathExtensions { - internal static bool Validate( - this ProviderInfo provider, - string path, - PSCmdlet cmdlet) + internal static bool ValidateProvider(this ProviderInfo provider) { if (provider.ImplementingType == typeof(FileSystemProvider)) { return true; } - ErrorRecord error = provider.ToInvalidProviderError(path); - cmdlet.WriteError(error); return false; } - internal static bool Exists(this string path) => - File.Exists(path) || Directory.Exists(path); + internal static string Normalize(this string path) + { + if (!path.ValidateExists()) + { + return path; + } + + path = path.TrimEnd(['\\', '/']); + + if (string.IsNullOrWhiteSpace(path)) + { + return Path.DirectorySeparatorChar.ToString(); + } + + if (Path.GetPathRoot(path) == path) + { + return string.Concat(path, Path.DirectorySeparatorChar); + } + + return path; + } + + internal static bool ValidateExists(this string path) + { + if (File.Exists(path) || Directory.Exists(path)) + { + return true; + } + + return false; + } internal static bool IsHidden(this FileSystemInfo item) => item.Attributes.HasFlag(FileAttributes.Hidden); From 2f25bcaf7043086f7eb67a9e4e0016ad6a8e73f1 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 14:50:23 -0300 Subject: [PATCH 05/19] woops typo --- src/PSTree/Extensions/PathExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs index 05c2146..6b75f16 100644 --- a/src/PSTree/Extensions/PathExtensions.cs +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -18,7 +18,7 @@ internal static bool ValidateProvider(this ProviderInfo provider) internal static string Normalize(this string path) { - if (!path.ValidateExists()) + if (path.ValidateExists()) { return path; } From 90f5dbd2e50acfedba4d50c654ef6d6afefd6340 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 15:09:18 -0300 Subject: [PATCH 06/19] another try, shit --- src/PSTree/CommandWithPathBase.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/PSTree/CommandWithPathBase.cs b/src/PSTree/CommandWithPathBase.cs index f6ba882..f1c378d 100644 --- a/src/PSTree/CommandWithPathBase.cs +++ b/src/PSTree/CommandWithPathBase.cs @@ -49,12 +49,12 @@ protected IEnumerable EnumerateResolvedPaths() Collection resolvedPaths; ProviderInfo provider; - foreach (string path in _paths ?? [SessionState.Path.CurrentLocation.Path]) + foreach (string path in NormalizePath(_paths ?? [SessionState.Path.CurrentLocation.Path])) { if (IsLiteral) { string resolved = SessionState.Path.GetUnresolvedProviderPathFromPSPath( - path: path.Normalize(), + path: path, provider: out provider, drive: out _); @@ -76,7 +76,7 @@ protected IEnumerable EnumerateResolvedPaths() try { - resolvedPaths = GetResolvedProviderPathFromPSPath(path.Normalize(), out provider); + resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider); } catch (Exception exception) { @@ -96,4 +96,12 @@ protected IEnumerable EnumerateResolvedPaths() } } } + + private static IEnumerable NormalizePath(string[] paths) + { + foreach (string path in paths) + { + yield return path.Normalize(); + } + } } From 1c2dcee9597a802b5ec31716b2882081b2fdfc4e Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 15:20:53 -0300 Subject: [PATCH 07/19] another try, shit ffffff --- src/PSTree/CommandWithPathBase.cs | 14 +++----------- src/PSTree/Extensions/PathExtensions.cs | 6 +++--- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/PSTree/CommandWithPathBase.cs b/src/PSTree/CommandWithPathBase.cs index f1c378d..93215df 100644 --- a/src/PSTree/CommandWithPathBase.cs +++ b/src/PSTree/CommandWithPathBase.cs @@ -49,12 +49,12 @@ protected IEnumerable EnumerateResolvedPaths() Collection resolvedPaths; ProviderInfo provider; - foreach (string path in NormalizePath(_paths ?? [SessionState.Path.CurrentLocation.Path])) + foreach (string path in _paths ?? [SessionState.Path.CurrentLocation.Path]) { if (IsLiteral) { string resolved = SessionState.Path.GetUnresolvedProviderPathFromPSPath( - path: path, + path: path.NormalizePath(), provider: out provider, drive: out _); @@ -76,7 +76,7 @@ protected IEnumerable EnumerateResolvedPaths() try { - resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider); + resolvedPaths = GetResolvedProviderPathFromPSPath(path.NormalizePath(), out provider); } catch (Exception exception) { @@ -96,12 +96,4 @@ protected IEnumerable EnumerateResolvedPaths() } } } - - private static IEnumerable NormalizePath(string[] paths) - { - foreach (string path in paths) - { - yield return path.Normalize(); - } - } } diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs index 6b75f16..3d844f3 100644 --- a/src/PSTree/Extensions/PathExtensions.cs +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -16,9 +16,9 @@ internal static bool ValidateProvider(this ProviderInfo provider) return false; } - internal static string Normalize(this string path) + internal static string NormalizePath(this string path) { - if (path.ValidateExists()) + if (!path.ValidateExists()) { return path; } @@ -32,7 +32,7 @@ internal static string Normalize(this string path) if (Path.GetPathRoot(path) == path) { - return string.Concat(path, Path.DirectorySeparatorChar); + return string.Concat(path.ToUpper(), Path.DirectorySeparatorChar); } return path; From ae18ad3883fef9af3524491b314cd80401344271 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 15:21:10 -0300 Subject: [PATCH 08/19] another try, shit ffffff --- src/PSTree/Extensions/PathExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs index 3d844f3..2900f46 100644 --- a/src/PSTree/Extensions/PathExtensions.cs +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -18,7 +18,7 @@ internal static bool ValidateProvider(this ProviderInfo provider) internal static string NormalizePath(this string path) { - if (!path.ValidateExists()) + if (path.ValidateExists()) { return path; } From 6588c0ed477a4733f888c3f57afc858201f849d5 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 16 Jan 2025 20:06:04 -0300 Subject: [PATCH 09/19] almost there, need to work on item counts --- src/PSTree/Cache.cs | 6 +- src/PSTree/CommandWithPathBase.cs | 10 +-- src/PSTree/Commands/GetPSTreeCommand.cs | 47 +++++++++++--- src/PSTree/Extensions/PathExtensions.cs | 83 ++----------------------- src/PSTree/PSTreeDirectory.cs | 16 ++++- src/PSTree/PSTreeFile.cs | 17 +++-- src/PSTree/PSTreeFileSystemInfo.cs | 4 ++ tests/GetPSTreeCommand.tests.ps1 | 12 ++-- 8 files changed, 86 insertions(+), 109 deletions(-) diff --git a/src/PSTree/Cache.cs b/src/PSTree/Cache.cs index 5427d8f..b31576b 100644 --- a/src/PSTree/Cache.cs +++ b/src/PSTree/Cache.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using PSTree.Extensions; namespace PSTree; @@ -22,7 +23,10 @@ internal void TryAddFiles() } } - internal PSTreeFileSystemInfo[] GetTree() => _items.ToArray().ConvertToTree(); + internal PSTreeFileSystemInfo[] GetTree() => _items + .Where(e => e._shouldInclude) + .ToArray() + .ConvertToTree(); internal void Clear() { diff --git a/src/PSTree/CommandWithPathBase.cs b/src/PSTree/CommandWithPathBase.cs index 93215df..94c4ca9 100644 --- a/src/PSTree/CommandWithPathBase.cs +++ b/src/PSTree/CommandWithPathBase.cs @@ -54,17 +54,17 @@ protected IEnumerable EnumerateResolvedPaths() if (IsLiteral) { string resolved = SessionState.Path.GetUnresolvedProviderPathFromPSPath( - path: path.NormalizePath(), + path: path, provider: out provider, drive: out _); - if (!provider.ValidateProvider()) + if (!provider.IsFileSystem()) { WriteError(provider.ToInvalidProviderError(resolved)); continue; } - if (!resolved.ValidateExists()) + if (!resolved.Exists()) { WriteError(resolved.ToInvalidPathError()); continue; @@ -76,7 +76,7 @@ protected IEnumerable EnumerateResolvedPaths() try { - resolvedPaths = GetResolvedProviderPathFromPSPath(path.NormalizePath(), out provider); + resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider); } catch (Exception exception) { @@ -86,7 +86,7 @@ protected IEnumerable EnumerateResolvedPaths() foreach (string resolved in resolvedPaths) { - if (!provider.ValidateProvider()) + if (!provider.IsFileSystem()) { WriteError(provider.ToInvalidProviderError(resolved)); continue; diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 3232254..62c38b2 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -64,8 +64,7 @@ protected override void BeginProcessing() _excludePatterns = [.. Exclude.Select(e => new WildcardPattern(e, options))]; } - // 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, options))]; } @@ -105,30 +104,33 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) bool keepProcessing = level <= Depth; foreach (FileSystemInfo item in next.GetSortedEnumerable(_comparer)) { - childCount++; - if (!Force.IsPresent && item.IsHidden()) { continue; } - if (item.ShouldExclude(_excludePatterns)) + if (ShouldExclude(item)) { continue; } - if (item is FileInfo file) + if (item is FileInfo fileInfo) { - size += file.Length; - if (Directory.IsPresent) { continue; } - if (keepProcessing && file.ShouldInclude(_includePatterns)) + if (keepProcessing && ShouldInclude(fileInfo)) { - _cache.AddFile(PSTreeFile.Create(file, source, level)); + childCount++; + size += fileInfo.Length; + + PSTreeFile file = PSTreeFile + .Create(fileInfo, source, level) + .WithParent(next); + + _cache.AddFile(file); } continue; @@ -140,6 +142,12 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) .Create((DirectoryInfo)item, source, level) .WithParent(next); + if (_includePatterns is null) + { + dir._shouldInclude = true; + childCount++; + } + _stack.Push(dir); } } @@ -171,4 +179,23 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) return _cache.GetTree(); } + + private static bool MatchAny(string name, WildcardPattern[] patterns) + { + foreach (WildcardPattern pattern in patterns) + { + if (pattern.IsMatch(name)) + { + return true; + } + } + + return false; + } + + private bool ShouldInclude(FileInfo item) => + _includePatterns is null || MatchAny(item.Name, _includePatterns); + + private bool ShouldExclude(FileSystemInfo item) => + _excludePatterns is not null && MatchAny(item.Name, _excludePatterns); } diff --git a/src/PSTree/Extensions/PathExtensions.cs b/src/PSTree/Extensions/PathExtensions.cs index 2900f46..2e2b9f6 100644 --- a/src/PSTree/Extensions/PathExtensions.cs +++ b/src/PSTree/Extensions/PathExtensions.cs @@ -6,87 +6,12 @@ namespace PSTree.Extensions; internal static class PathExtensions { - internal static bool ValidateProvider(this ProviderInfo provider) - { - if (provider.ImplementingType == typeof(FileSystemProvider)) - { - return true; - } + internal static bool IsFileSystem(this ProviderInfo provider) => + provider.ImplementingType == typeof(FileSystemProvider); - return false; - } - - internal static string NormalizePath(this string path) - { - if (path.ValidateExists()) - { - return path; - } - - path = path.TrimEnd(['\\', '/']); - - if (string.IsNullOrWhiteSpace(path)) - { - return Path.DirectorySeparatorChar.ToString(); - } - - if (Path.GetPathRoot(path) == path) - { - return string.Concat(path.ToUpper(), Path.DirectorySeparatorChar); - } - - return path; - } - - internal static bool ValidateExists(this string path) - { - if (File.Exists(path) || Directory.Exists(path)) - { - return true; - } - - return false; - } + 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); - - private static bool MatchAny( - FileSystemInfo item, - WildcardPattern[] patterns) - { - foreach (WildcardPattern pattern in patterns) - { - if (pattern.IsMatch(item.FullName)) - { - return true; - } - } - - return false; - } - - internal static bool ShouldInclude( - this FileInfo file, - WildcardPattern[]? patterns) - { - if (patterns is null) - { - return true; - } - - return MatchAny(file, patterns); - } - - internal static bool ShouldExclude( - this FileSystemInfo item, - WildcardPattern[]? patterns) - { - if (patterns is null) - { - return false; - } - - return MatchAny(item, patterns); - } } diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index bcf909e..7f6b8e4 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -8,8 +8,6 @@ namespace PSTree; public sealed class PSTreeDirectory : PSTreeFileSystemInfo { - private PSTreeDirectory? _parent; - public DirectoryInfo Parent => Instance.Parent; public int ItemCount { get; internal set; } @@ -79,4 +77,18 @@ internal void IndexLength(long length) parent.Length += length; } } + + internal void SetIncludeFlag() + { + _shouldInclude = true; + for (PSTreeDirectory? parent = _parent; parent is not null; parent = parent._parent) + { + if (parent._shouldInclude) + { + return; + } + + parent._shouldInclude = true; + } + } } diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index cb81da0..918aecf 100644 --- a/src/PSTree/PSTreeFile.cs +++ b/src/PSTree/PSTreeFile.cs @@ -11,17 +11,19 @@ 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(string path) => Create(new FileInfo(path), path); @@ -37,4 +39,11 @@ 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 WithParent(PSTreeDirectory parent) + { + _parent = parent; + _parent.SetIncludeFlag(); + return this; + } } diff --git a/src/PSTree/PSTreeFileSystemInfo.cs b/src/PSTree/PSTreeFileSystemInfo.cs index e279a44..684c68e 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) { + protected PSTreeDirectory? _parent; + + internal bool _shouldInclude; + internal string Source { get; set; } = source; public int Depth { get; protected set; } diff --git a/tests/GetPSTreeCommand.tests.ps1 b/tests/GetPSTreeCommand.tests.ps1 index 27d69af..db43633 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 { @@ -119,7 +115,7 @@ Describe 'Get-PSTree' { } | Should -Not -BeTrue } - 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( @@ -138,7 +134,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 From ffc7c85625f15697c1579ede00a1a636179ac6cc Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 17 Jan 2025 13:09:04 -0300 Subject: [PATCH 10/19] almost there, need to work on item counts --- src/PSTree/Cache.cs | 14 +++++++------- src/PSTree/Commands/GetPSTreeCommand.cs | 21 ++++++++++++++------- src/PSTree/Extensions/TreeExtensions.cs | 1 + src/PSTree/PSTreeDirectory.cs | 12 +++++++----- src/PSTree/PSTreeFile.cs | 17 ++++++++++++----- src/PSTree/PSTreeFileSystemInfo.cs | 2 +- tests/GetPSTreeCommand.tests.ps1 | 8 ++++++++ 7 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/PSTree/Cache.cs b/src/PSTree/Cache.cs index b31576b..45dc77a 100644 --- a/src/PSTree/Cache.cs +++ b/src/PSTree/Cache.cs @@ -10,11 +10,11 @@ internal sealed class Cache private readonly List _files = []; - internal void AddFile(PSTreeFile file) => _files.Add(file); + internal void Add(PSTreeFile file) => _files.Add(file); - internal void Add(PSTreeFileSystemInfo item) => _items.Add(item); + internal void Add(PSTreeDirectory directory) => _items.Add(directory); - internal void TryAddFiles() + internal void Flush() { if (_files.Count > 0) { @@ -23,10 +23,10 @@ internal void TryAddFiles() } } - internal PSTreeFileSystemInfo[] GetTree() => _items - .Where(e => e._shouldInclude) - .ToArray() - .ConvertToTree(); + internal PSTreeFileSystemInfo[] GetTree(bool filterInclude) => + filterInclude + ? _items.Where(e => e.ShouldInclude).ToArray().ConvertToTree() + : _items.ToArray().ConvertToTree(); internal void Clear() { diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 62c38b2..a08a737 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -76,7 +76,12 @@ protected override void ProcessRecord() { if (File.Exists(path)) { - WriteObject(PSTreeFile.Create(path)); + FileInfo file = new(path); + if (!ShouldExclude(file) && ShouldInclude(file)) + { + WriteObject(PSTreeFile.Create(file, path)); + } + continue; } @@ -118,6 +123,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) { if (Directory.IsPresent) { + size += fileInfo.Length; continue; } @@ -128,9 +134,10 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) PSTreeFile file = PSTreeFile .Create(fileInfo, source, level) - .WithParent(next); + .WithParent(next) + .WithIncludeFlagIf(_includePatterns is not null); - _cache.AddFile(file); + _cache.Add(file); } continue; @@ -144,7 +151,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) if (_includePatterns is null) { - dir._shouldInclude = true; + dir.ShouldInclude = true; childCount++; } @@ -153,7 +160,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) } next.Length = size; - next.IndexItemCount(childCount); + next.IndexCount(childCount); if (RecursiveSize.IsPresent) { @@ -163,7 +170,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) if (next.Depth <= Depth) { _cache.Add(next); - _cache.TryAddFiles(); + _cache.Flush(); } } catch (Exception exception) @@ -177,7 +184,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) } } - return _cache.GetTree(); + return _cache.GetTree(_includePatterns is not null); } private static bool MatchAny(string name, WildcardPattern[] patterns) diff --git a/src/PSTree/Extensions/TreeExtensions.cs b/src/PSTree/Extensions/TreeExtensions.cs index d2cce5c..8c67801 100644 --- a/src/PSTree/Extensions/TreeExtensions.cs +++ b/src/PSTree/Extensions/TreeExtensions.cs @@ -21,6 +21,7 @@ internal static PSTreeFileSystemInfo[] ConvertToTree( { int index; PSTreeFileSystemInfo current; + for (int i = 0; i < inputObject.Length; i++) { current = inputObject[i]; diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index 7f6b8e4..270e884 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -22,7 +22,9 @@ private PSTreeDirectory( private PSTreeDirectory( DirectoryInfo dir, string hierarchy, string source) : base(dir, hierarchy, source) - { } + { + ShouldInclude = true; + } public IEnumerable EnumerateFiles() => Instance.EnumerateFiles(); @@ -59,7 +61,7 @@ internal PSTreeDirectory WithParent(PSTreeDirectory parent) return this; } - internal void IndexItemCount(int count) + internal void IndexCount(int count) { ItemCount = count; TotalItemCount = count; @@ -80,15 +82,15 @@ internal void IndexLength(long length) internal void SetIncludeFlag() { - _shouldInclude = true; + ShouldInclude = true; for (PSTreeDirectory? parent = _parent; parent is not null; parent = parent._parent) { - if (parent._shouldInclude) + if (parent.ShouldInclude) { return; } - parent._shouldInclude = true; + parent.ShouldInclude = true; } } } diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index 918aecf..2ba972e 100644 --- a/src/PSTree/PSTreeFile.cs +++ b/src/PSTree/PSTreeFile.cs @@ -15,7 +15,7 @@ private PSTreeFile( : base(file, hierarchy, source) { Length = file.Length; - _shouldInclude = true; + ShouldInclude = true; } private PSTreeFile( @@ -23,11 +23,9 @@ private PSTreeFile( : base(file, hierarchy, source, depth) { Length = file.Length; - _shouldInclude = true; + ShouldInclude = true; } - internal static PSTreeFile Create(string path) => Create(new FileInfo(path), path); - internal static PSTreeFile Create(FileInfo file, string source) { string styled = TreeStyle.Instance.GetColoredName(file); @@ -43,7 +41,16 @@ internal static PSTreeFile Create(FileInfo file, string source, int depth) internal PSTreeFile WithParent(PSTreeDirectory parent) { _parent = parent; - _parent.SetIncludeFlag(); + return this; + } + + internal PSTreeFile WithIncludeFlagIf(bool condition) + { + if (condition) + { + _parent?.SetIncludeFlag(); + } + return this; } } diff --git a/src/PSTree/PSTreeFileSystemInfo.cs b/src/PSTree/PSTreeFileSystemInfo.cs index 684c68e..b4d40e7 100644 --- a/src/PSTree/PSTreeFileSystemInfo.cs +++ b/src/PSTree/PSTreeFileSystemInfo.cs @@ -4,7 +4,7 @@ public abstract class PSTreeFileSystemInfo(string hierarchy, string source) { protected PSTreeDirectory? _parent; - internal bool _shouldInclude; + internal bool ShouldInclude { get; set; } internal string Source { get; set; } = source; diff --git a/tests/GetPSTreeCommand.tests.ps1 b/tests/GetPSTreeCommand.tests.ps1 index db43633..9a41ced 100644 --- a/tests/GetPSTreeCommand.tests.ps1 +++ b/tests/GetPSTreeCommand.tests.ps1 @@ -113,6 +113,10 @@ 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 'Includes child items with -Include parameter' { @@ -125,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' { From 6a880b2f459071e12a7eb706999d902907ef38f3 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 17 Jan 2025 18:03:06 -0300 Subject: [PATCH 11/19] almost there, this is hard --- docs/en-US/Get-PSTree.md | 6 ++--- src/PSTree/Cache.cs | 13 +++++++++- src/PSTree/Commands/GetPSTreeCommand.cs | 34 +++++++++++++++++++------ src/PSTree/PSTreeDirectory.cs | 31 ++++++++++++++++------ src/PSTree/PSTreeFile.cs | 2 +- src/PSTree/Style/Extension.cs | 2 +- 6 files changed, 66 insertions(+), 22 deletions(-) diff --git a/docs/en-US/Get-PSTree.md b/docs/en-US/Get-PSTree.md index b7d6e1a..266bbfe 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] @@ -110,7 +110,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: diff --git a/src/PSTree/Cache.cs b/src/PSTree/Cache.cs index 45dc77a..10ca8d9 100644 --- a/src/PSTree/Cache.cs +++ b/src/PSTree/Cache.cs @@ -25,7 +25,18 @@ internal void Flush() internal PSTreeFileSystemInfo[] GetTree(bool filterInclude) => filterInclude - ? _items.Where(e => e.ShouldInclude).ToArray().ConvertToTree() + ? _items + .Where(static e => + { + if (e.ShouldInclude && e is PSTreeDirectory dir) + { + dir.IncrementItemCount(); + } + + return e.ShouldInclude; + }) + .ToArray() + .ConvertToTree() : _items.ToArray().ConvertToTree(); internal void Clear() diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index a08a737..e15885d 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -50,7 +50,7 @@ public sealed class GetPSTreeCommand : CommandWithPathBase protected override void BeginProcessing() { - if (Recurse.IsPresent && !MyInvocation.BoundParameters.ContainsKey("Depth")) + if (Recurse && !MyInvocation.BoundParameters.ContainsKey("Depth")) { Depth = int.MaxValue; } @@ -109,7 +109,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) bool keepProcessing = level <= Depth; foreach (FileSystemInfo item in next.GetSortedEnumerable(_comparer)) { - if (!Force.IsPresent && item.IsHidden()) + if (!Force && item.IsHidden()) { continue; } @@ -121,33 +121,41 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) if (item is FileInfo fileInfo) { - if (Directory.IsPresent) + if (Directory) { size += fileInfo.Length; continue; } - if (keepProcessing && ShouldInclude(fileInfo)) + bool include = ShouldInclude(fileInfo); + if (keepProcessing && include) { childCount++; size += fileInfo.Length; PSTreeFile file = PSTreeFile .Create(fileInfo, source, level) - .WithParent(next) + .AddParent(next) .WithIncludeFlagIf(_includePatterns is not null); _cache.Add(file); + continue; + } + + if (RecursiveSize && include) + { + size += fileInfo.Length; + // childCount++; } continue; } - if (keepProcessing || RecursiveSize.IsPresent) + if (keepProcessing) { PSTreeDirectory dir = PSTreeDirectory .Create((DirectoryInfo)item, source, level) - .WithParent(next); + .AddParent(next); if (_includePatterns is null) { @@ -155,6 +163,16 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) childCount++; } + _stack.Push(dir); + continue; + } + + if (RecursiveSize) + { + PSTreeDirectory dir = PSTreeDirectory + .Create((DirectoryInfo)item, source, level) + .AddParent(next); + _stack.Push(dir); } } @@ -162,7 +180,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) next.Length = size; next.IndexCount(childCount); - if (RecursiveSize.IsPresent) + if (RecursiveSize) { next.IndexLength(size); } diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index 270e884..fc61169 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -55,7 +55,7 @@ internal static PSTreeDirectory Create(DirectoryInfo dir, string source, int dep return new PSTreeDirectory(dir, styled, source, depth); } - internal PSTreeDirectory WithParent(PSTreeDirectory parent) + internal PSTreeDirectory AddParent(PSTreeDirectory parent) { _parent = parent; return this; @@ -66,31 +66,46 @@ internal void IndexCount(int count) ItemCount = count; TotalItemCount = count; - for (PSTreeDirectory? parent = _parent; parent is not null; parent = parent._parent) + for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) { - parent.TotalItemCount += count; + i.TotalItemCount += count; } } internal void IndexLength(long length) { - for (PSTreeDirectory? parent = _parent; parent is not null; parent = parent._parent) + Length += length; + for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) { - parent.Length += length; + i.Length += length; } } internal void SetIncludeFlag() { ShouldInclude = true; - for (PSTreeDirectory? parent = _parent; parent is not null; parent = parent._parent) + for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) { - if (parent.ShouldInclude) + if (i.ShouldInclude) { return; } - parent.ShouldInclude = true; + i.ShouldInclude = true; + } + } + + internal void IncrementItemCount() + { + if (_parent is null) + { + return; + } + + _parent.ItemCount++; + for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) + { + i.TotalItemCount++; } } } diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index 2ba972e..fc22292 100644 --- a/src/PSTree/PSTreeFile.cs +++ b/src/PSTree/PSTreeFile.cs @@ -38,7 +38,7 @@ internal static PSTreeFile Create(FileInfo file, string source, int depth) return new PSTreeFile(file, styled, source, depth); } - internal PSTreeFile WithParent(PSTreeDirectory parent) + internal PSTreeFile AddParent(PSTreeDirectory parent) { _parent = parent; return this; diff --git a/src/PSTree/Style/Extension.cs b/src/PSTree/Style/Extension.cs index 8adf5e9..0bd2635 100644 --- a/src/PSTree/Style/Extension.cs +++ b/src/PSTree/Style/Extension.cs @@ -48,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 From 5d49f5ece7f1c5e6e85166145f0296866ad0e7f8 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 18 Jan 2025 10:43:58 -0300 Subject: [PATCH 12/19] i think i got it --- src/PSTree/Commands/GetPSTreeCommand.cs | 3 +-- src/PSTree/PSTreeDirectory.cs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index e15885d..c21f6fb 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -145,7 +145,6 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) if (RecursiveSize && include) { size += fileInfo.Length; - // childCount++; } continue; @@ -157,7 +156,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) .Create((DirectoryInfo)item, source, level) .AddParent(next); - if (_includePatterns is null) + if (Directory || _includePatterns is null) { dir.ShouldInclude = true; childCount++; diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index fc61169..9a8d715 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -74,7 +74,6 @@ internal void IndexCount(int count) internal void IndexLength(long length) { - Length += length; for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) { i.Length += length; @@ -84,6 +83,7 @@ internal void IndexLength(long length) internal void SetIncludeFlag() { ShouldInclude = true; + for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) { if (i.ShouldInclude) @@ -103,6 +103,7 @@ internal void IncrementItemCount() } _parent.ItemCount++; + for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) { i.TotalItemCount++; From 6f2d3d740ff28861d3c3453c5fb05334277f03a6 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 18 Jan 2025 10:58:48 -0300 Subject: [PATCH 13/19] one more test --- tests/GetPSTreeCommand.tests.ps1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/GetPSTreeCommand.tests.ps1 b/tests/GetPSTreeCommand.tests.ps1 index 9a41ced..aa838dd 100644 --- a/tests/GetPSTreeCommand.tests.ps1 +++ b/tests/GetPSTreeCommand.tests.ps1 @@ -157,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 From 9f9622bc81d0ded9e780a3ad560c065eb4792571 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 18 Jan 2025 13:06:21 -0300 Subject: [PATCH 14/19] a few code improvements --- src/PSTree/Commands/GetPSTreeCommand.cs | 16 ++++++--- src/PSTree/Extensions/TreeExtensions.cs | 43 +++++++++++++------------ src/PSTree/PSTreeFile.cs | 2 +- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index c21f6fb..146c48c 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -12,6 +12,10 @@ namespace PSTree.Commands; [Alias("pstree")] public sealed class GetPSTreeCommand : CommandWithPathBase { + private bool _withExclude; + + private bool _withInclude; + private WildcardPattern[]? _excludePatterns; private WildcardPattern[]? _includePatterns; @@ -62,11 +66,13 @@ protected override void BeginProcessing() if (Exclude is not null) { _excludePatterns = [.. Exclude.Select(e => new WildcardPattern(e, options))]; + _withExclude = true; } if (Include is not null) { _includePatterns = [.. Include.Select(e => new WildcardPattern(e, options))]; + _withInclude = true; } } @@ -136,7 +142,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) PSTreeFile file = PSTreeFile .Create(fileInfo, source, level) .AddParent(next) - .WithIncludeFlagIf(_includePatterns is not null); + .SetIncludeFlagIf(_withInclude); _cache.Add(file); continue; @@ -156,7 +162,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) .Create((DirectoryInfo)item, source, level) .AddParent(next); - if (Directory || _includePatterns is null) + if (Directory || !_withInclude) { dir.ShouldInclude = true; childCount++; @@ -201,7 +207,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) } } - return _cache.GetTree(_includePatterns is not null); + return _cache.GetTree(_withInclude); } private static bool MatchAny(string name, WildcardPattern[] patterns) @@ -218,8 +224,8 @@ private static bool MatchAny(string name, WildcardPattern[] patterns) } private bool ShouldInclude(FileInfo item) => - _includePatterns is null || MatchAny(item.Name, _includePatterns); + !_withInclude || MatchAny(item.Name, _includePatterns!); private bool ShouldExclude(FileSystemInfo item) => - _excludePatterns is not null && MatchAny(item.Name, _excludePatterns); + _withExclude && MatchAny(item.Name, _excludePatterns!); } diff --git a/src/PSTree/Extensions/TreeExtensions.cs b/src/PSTree/Extensions/TreeExtensions.cs index 8c67801..ce674e8 100644 --- a/src/PSTree/Extensions/TreeExtensions.cs +++ b/src/PSTree/Extensions/TreeExtensions.cs @@ -1,13 +1,16 @@ -using System.Text; +using System; +using System.Text; namespace PSTree.Extensions; internal static class TreeExtensions { - private static readonly StringBuilder s_sb = new(); + [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) @@ -20,43 +23,41 @@ internal static PSTreeFileSystemInfo[] ConvertToTree( this PSTreeFileSystemInfo[] inputObject) { int index; - PSTreeFileSystemInfo current; - for (int i = 0; i < inputObject.Length; i++) { - current = inputObject[i]; + PSTreeFileSystemInfo current = inputObject[i]; if ((index = current.Hierarchy.IndexOf('└')) == -1) { continue; } - int z; - char[] replace; - for (z = i - 1; z >= 0; z--) + for (int z = i - 1; z >= 0; z--) { current = inputObject[z]; - if (!char.IsWhiteSpace(current.Hierarchy[index])) + string hierarchy = current.Hierarchy; + + if (char.IsWhiteSpace(hierarchy[index])) { - UpdateCorner(index, current); - break; + current.Hierarchy = hierarchy.ReplaceAt(index, '│'); + continue; } - replace = current.Hierarchy.ToCharArray(); - replace[index] = '│'; - current.Hierarchy = new string(replace); + if (hierarchy[index] == '└') + { + current.Hierarchy = hierarchy.ReplaceAt(index, '├'); + } + + break; } } return inputObject; } - private static void UpdateCorner(int index, PSTreeFileSystemInfo current) + private static string ReplaceAt(this string input, int index, char newChar) { - if (current.Hierarchy[index] == '└') - { - char[] replace = current.Hierarchy.ToCharArray(); - replace[index] = '├'; - current.Hierarchy = new string(replace); - } + char[] chars = input.ToCharArray(); + chars[index] = newChar; + return new string(chars); } } diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index fc22292..667914a 100644 --- a/src/PSTree/PSTreeFile.cs +++ b/src/PSTree/PSTreeFile.cs @@ -44,7 +44,7 @@ internal PSTreeFile AddParent(PSTreeDirectory parent) return this; } - internal PSTreeFile WithIncludeFlagIf(bool condition) + internal PSTreeFile SetIncludeFlagIf(bool condition) { if (condition) { From c3a1665402e2951d8000df93f443f19d8963e6e5 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 19 Jan 2025 18:04:11 -0300 Subject: [PATCH 15/19] a few code improvements. need to update changelog, readme and docs --- module/PSTree.psd1 | 2 +- src/PSTree/Cache.cs | 29 ++++++----- src/PSTree/Commands/GetPSTreeCommand.cs | 67 +++++++++++-------------- src/PSTree/Extensions/TreeExtensions.cs | 11 ++-- src/PSTree/PSTreeDirectory.cs | 3 +- tests/PSTreeDirectory.tests.ps1 | 7 ++- 6 files changed, 56 insertions(+), 63 deletions(-) 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 index 10ca8d9..5489d2f 100644 --- a/src/PSTree/Cache.cs +++ b/src/PSTree/Cache.cs @@ -23,21 +23,20 @@ internal void Flush() } } - internal PSTreeFileSystemInfo[] GetTree(bool filterInclude) => - filterInclude - ? _items - .Where(static e => - { - if (e.ShouldInclude && e is PSTreeDirectory dir) - { - dir.IncrementItemCount(); - } - - return e.ShouldInclude; - }) - .ToArray() - .ConvertToTree() - : _items.ToArray().ConvertToTree(); + internal PSTreeFileSystemInfo[] GetTree(bool condition) => + condition + ? _items.Where(IsIncluded).ToArray().Format() + : _items.ToArray().Format(); + + private static bool IsIncluded(PSTreeFileSystemInfo item) + { + if (item.ShouldInclude && item is PSTreeDirectory dir) + { + dir.IncrementItemCount(); + } + + return item.ShouldInclude; + } internal void Clear() { diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 146c48c..602ab28 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -107,7 +107,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) { PSTreeDirectory next = _stack.Pop(); int level = next.Depth + 1; - long size = 0; + long totalLength = 0; int childCount = 0; try @@ -115,12 +115,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) bool keepProcessing = level <= Depth; foreach (FileSystemInfo item in next.GetSortedEnumerable(_comparer)) { - if (!Force && item.IsHidden()) - { - continue; - } - - if (ShouldExclude(item)) + if (!Force && item.IsHidden() || ShouldExclude(item)) { continue; } @@ -129,15 +124,25 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) { if (Directory) { - size += fileInfo.Length; + totalLength += fileInfo.Length; continue; } - bool include = ShouldInclude(fileInfo); - if (keepProcessing && include) + if (!ShouldInclude(fileInfo)) + { + continue; + } + + if (!keepProcessing && !RecursiveSize) + { + continue; + } + + totalLength += fileInfo.Length; + + if (keepProcessing) { childCount++; - size += fileInfo.Length; PSTreeFile file = PSTreeFile .Create(fileInfo, source, level) @@ -145,49 +150,35 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) .SetIncludeFlagIf(_withInclude); _cache.Add(file); - continue; - } - - if (RecursiveSize && include) - { - size += fileInfo.Length; } continue; } - if (keepProcessing) + if (!keepProcessing && !RecursiveSize) { - PSTreeDirectory dir = PSTreeDirectory - .Create((DirectoryInfo)item, source, level) - .AddParent(next); - - if (Directory || !_withInclude) - { - dir.ShouldInclude = true; - childCount++; - } - - _stack.Push(dir); continue; } - if (RecursiveSize) - { - PSTreeDirectory dir = PSTreeDirectory - .Create((DirectoryInfo)item, source, level) - .AddParent(next); + PSTreeDirectory dir = PSTreeDirectory + .Create((DirectoryInfo)item, source, level) + .AddParent(next); - _stack.Push(dir); + if (keepProcessing && Directory || !_withInclude) + { + dir.ShouldInclude = true; + childCount++; } + + _stack.Push(dir); } - next.Length = size; + next.Length = totalLength; next.IndexCount(childCount); if (RecursiveSize) { - next.IndexLength(size); + next.IndexLength(totalLength); } if (next.Depth <= Depth) @@ -207,7 +198,7 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) } } - return _cache.GetTree(_withInclude); + return _cache.GetTree(_withInclude && !Directory); } private static bool MatchAny(string name, WildcardPattern[] patterns) diff --git a/src/PSTree/Extensions/TreeExtensions.cs b/src/PSTree/Extensions/TreeExtensions.cs index ce674e8..7988f9a 100644 --- a/src/PSTree/Extensions/TreeExtensions.cs +++ b/src/PSTree/Extensions/TreeExtensions.cs @@ -19,13 +19,12 @@ internal static string Indent(this string inputString, int indentation) .ToString(); } - internal static PSTreeFileSystemInfo[] ConvertToTree( - this PSTreeFileSystemInfo[] inputObject) + internal static PSTreeFileSystemInfo[] Format(this PSTreeFileSystemInfo[] tree) { int index; - for (int i = 0; i < inputObject.Length; i++) + for (int i = 0; i < tree.Length; i++) { - PSTreeFileSystemInfo current = inputObject[i]; + PSTreeFileSystemInfo current = tree[i]; if ((index = current.Hierarchy.IndexOf('└')) == -1) { continue; @@ -33,7 +32,7 @@ internal static PSTreeFileSystemInfo[] ConvertToTree( for (int z = i - 1; z >= 0; z--) { - current = inputObject[z]; + current = tree[z]; string hierarchy = current.Hierarchy; if (char.IsWhiteSpace(hierarchy[index])) @@ -51,7 +50,7 @@ internal static PSTreeFileSystemInfo[] ConvertToTree( } } - return inputObject; + return tree; } private static string ReplaceAt(this string input, int index, char newChar) diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index 9a8d715..5d77f1d 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -41,7 +41,8 @@ internal IOrderedEnumerable GetSortedEnumerable(TreeComparer com .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(string path) => + Create(new DirectoryInfo(path), path); internal static PSTreeDirectory Create(DirectoryInfo dir, string source) { 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 } } From 4ee8be4e6e4316b1aa1257a26516740e376a2845 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 19 Jan 2025 18:50:55 -0300 Subject: [PATCH 16/19] updates to changelog, readme and docs --- CHANGELOG.md | 74 ++++++++++++++++++++++++ README.md | 120 ++++++++++++++++++--------------------- docs/en-US/Get-PSTree.md | 14 +++-- 3 files changed, 140 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a515203..f84819a 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`. 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 266bbfe..b3a3a56 100644 --- a/docs/en-US/Get-PSTree.md +++ b/docs/en-US/Get-PSTree.md @@ -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 From 1d05ca3d3c3f9b43c2ee135645e90fdb0dda9f9d Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 19 Jan 2025 18:55:16 -0300 Subject: [PATCH 17/19] updates to changelog, readme and docs --- docs/en-US/Get-PSTree.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/en-US/Get-PSTree.md b/docs/en-US/Get-PSTree.md index b3a3a56..7a554a2 100644 --- a/docs/en-US/Get-PSTree.md +++ b/docs/en-US/Get-PSTree.md @@ -153,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[] @@ -192,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[] @@ -228,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[] @@ -237,7 +238,7 @@ Aliases: Required: False Position: 0 -Default value: Current directory +Default value: $PWD Accept pipeline input: True (ByValue) Accept wildcard characters: True ``` From e04d1e958aa50ee1f0a89baa4547b1f2464144ae Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 19 Jan 2025 18:56:52 -0300 Subject: [PATCH 18/19] updates to changelog, readme and docs --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f84819a..959a5fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,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 @@ -349,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 From 3567b8b7e623f2cd70ebeb62c8199feb9e3f909d Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 19 Jan 2025 19:56:09 -0300 Subject: [PATCH 19/19] simplifies logic to get item and totalitem count --- src/PSTree/Cache.cs | 32 ++++++++++++++++++------- src/PSTree/Commands/GetPSTreeCommand.cs | 5 ---- src/PSTree/Extensions/TreeExtensions.cs | 12 +++++++++- src/PSTree/PSTreeDirectory.cs | 23 ++++-------------- src/PSTree/PSTreeFile.cs | 4 ++-- src/PSTree/PSTreeFileSystemInfo.cs | 2 +- 6 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/PSTree/Cache.cs b/src/PSTree/Cache.cs index 5489d2f..b9c584a 100644 --- a/src/PSTree/Cache.cs +++ b/src/PSTree/Cache.cs @@ -23,19 +23,35 @@ internal void Flush() } } - internal PSTreeFileSystemInfo[] GetTree(bool condition) => - condition - ? _items.Where(IsIncluded).ToArray().Format() - : _items.ToArray().Format(); + internal PSTreeFileSystemInfo[] GetTree(bool condition) + { + PSTreeFileSystemInfo[] result = condition + ? [.. _items.Where(static e => e.ShouldInclude)] + : [.. _items]; + + return result.Format(GetItemCount(result)); + } - private static bool IsIncluded(PSTreeFileSystemInfo item) + private static Dictionary GetItemCount(PSTreeFileSystemInfo[] items) { - if (item.ShouldInclude && item is PSTreeDirectory dir) + Dictionary counts = []; + foreach (PSTreeFileSystemInfo item in items) { - dir.IncrementItemCount(); + string? path = item.ParentNode?.FullName; + if (path is null) + { + continue; + } + + if (!counts.ContainsKey(path)) + { + counts[path] = 0; + } + + counts[path]++; } - return item.ShouldInclude; + return counts; } internal void Clear() diff --git a/src/PSTree/Commands/GetPSTreeCommand.cs b/src/PSTree/Commands/GetPSTreeCommand.cs index 602ab28..86aa937 100644 --- a/src/PSTree/Commands/GetPSTreeCommand.cs +++ b/src/PSTree/Commands/GetPSTreeCommand.cs @@ -108,7 +108,6 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) PSTreeDirectory next = _stack.Pop(); int level = next.Depth + 1; long totalLength = 0; - int childCount = 0; try { @@ -142,8 +141,6 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) if (keepProcessing) { - childCount++; - PSTreeFile file = PSTreeFile .Create(fileInfo, source, level) .AddParent(next) @@ -167,14 +164,12 @@ private PSTreeFileSystemInfo[] Traverse(PSTreeDirectory directory) if (keepProcessing && Directory || !_withInclude) { dir.ShouldInclude = true; - childCount++; } _stack.Push(dir); } next.Length = totalLength; - next.IndexCount(childCount); if (RecursiveSize) { diff --git a/src/PSTree/Extensions/TreeExtensions.cs b/src/PSTree/Extensions/TreeExtensions.cs index 7988f9a..f09dfee 100644 --- a/src/PSTree/Extensions/TreeExtensions.cs +++ b/src/PSTree/Extensions/TreeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text; namespace PSTree.Extensions; @@ -19,12 +20,21 @@ internal static string Indent(this string inputString, int indentation) .ToString(); } - internal static PSTreeFileSystemInfo[] Format(this PSTreeFileSystemInfo[] tree) + 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; diff --git a/src/PSTree/PSTreeDirectory.cs b/src/PSTree/PSTreeDirectory.cs index 5d77f1d..9bac7ea 100644 --- a/src/PSTree/PSTreeDirectory.cs +++ b/src/PSTree/PSTreeDirectory.cs @@ -58,7 +58,7 @@ internal static PSTreeDirectory Create(DirectoryInfo dir, string source, int dep internal PSTreeDirectory AddParent(PSTreeDirectory parent) { - _parent = parent; + ParentNode = parent; return this; } @@ -67,7 +67,7 @@ internal void IndexCount(int count) ItemCount = count; TotalItemCount = count; - for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) + for (PSTreeDirectory? i = ParentNode; i is not null; i = i.ParentNode) { i.TotalItemCount += count; } @@ -75,7 +75,7 @@ internal void IndexCount(int count) internal void IndexLength(long length) { - for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) + for (PSTreeDirectory? i = ParentNode; i is not null; i = i.ParentNode) { i.Length += length; } @@ -85,7 +85,7 @@ internal void SetIncludeFlag() { ShouldInclude = true; - for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) + for (PSTreeDirectory? i = ParentNode; i is not null; i = i.ParentNode) { if (i.ShouldInclude) { @@ -95,19 +95,4 @@ internal void SetIncludeFlag() i.ShouldInclude = true; } } - - internal void IncrementItemCount() - { - if (_parent is null) - { - return; - } - - _parent.ItemCount++; - - for (PSTreeDirectory? i = _parent; i is not null; i = i._parent) - { - i.TotalItemCount++; - } - } } diff --git a/src/PSTree/PSTreeFile.cs b/src/PSTree/PSTreeFile.cs index 667914a..8a8e289 100644 --- a/src/PSTree/PSTreeFile.cs +++ b/src/PSTree/PSTreeFile.cs @@ -40,7 +40,7 @@ internal static PSTreeFile Create(FileInfo file, string source, int depth) internal PSTreeFile AddParent(PSTreeDirectory parent) { - _parent = parent; + ParentNode = parent; return this; } @@ -48,7 +48,7 @@ internal PSTreeFile SetIncludeFlagIf(bool condition) { if (condition) { - _parent?.SetIncludeFlag(); + ParentNode?.SetIncludeFlag(); } return this; diff --git a/src/PSTree/PSTreeFileSystemInfo.cs b/src/PSTree/PSTreeFileSystemInfo.cs index b4d40e7..9c36ab8 100644 --- a/src/PSTree/PSTreeFileSystemInfo.cs +++ b/src/PSTree/PSTreeFileSystemInfo.cs @@ -2,7 +2,7 @@ namespace PSTree; public abstract class PSTreeFileSystemInfo(string hierarchy, string source) { - protected PSTreeDirectory? _parent; + internal PSTreeDirectory? ParentNode { get; set; } internal bool ShouldInclude { get; set; }