PowerShellUtils/Commands/PrintTree/PrintTreeService.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using PowerShellStandardModule1.Lib;
using PowerShellStandardModule1.Lib.Extensions;
 
namespace PowerShellStandardModule1.Commands.PrintTree;
 
public partial class PrintTreeService
{
    public DirectoryInfo StartingDirectory { get; }
    public StringValueSelector StringValueSelector { get; }
    public int Height { get; }
    public int Width { get; }
    public int NodeWidth { get; }
    public int Limit { get; }
    public CancellationToken Token { get; }
    public int RootNodeWidth { get; }
    public string OrderBy { get; }
    public bool Descending { get; }
    public Func<FileSystemInfo, bool> Filter { get; }
    public bool Within { get; }
    public bool File { get; }
 
    public bool Leaf { get; }
 
    public int ParallelThreshold { get; }
}
 
public partial class PrintTreeService
{
    private Func<FileSystemInfo, IEnumerable<FileSystemInfo>> ChildProvider =>
        File
            ? FsUtil.GetChildren
            : DirectoryUtil.GetChildren;
 
 
    private BfsExecutor<FileSystemInfo> BfsImpl { get; set; }
 
    private PrintNodeImpl PrintNodeImpl { get; set; }
 
    private RemoveBranchesExceptFilteredImpl RemoveBranchesExceptFilteredImpl { get; set; }
 
    private ClearChildrenExceedingWidthImpl ClearChildrenExceedingWidthImpl { get; set; }
 
    private ClearLeavesFailingFilterImpl ClearLeavesFailingFilterImpl { get; set; }
 
 
    public PrintTreeService(
        DirectoryInfo startingDirectory,
        StringValueSelector? stringValueSelector = null,
        int height = 3,
        int width = int.MaxValue,
        int nodeWidth = int.MaxValue,
        int limit = int.MaxValue,
        int rootNodeWidth = -1,
        string orderBy = "Name",
        bool descending = false,
        Func<FileSystemInfo, bool>? filter = null,
        bool within = false,
        bool file = false,
        int parallelThreshold = 10_000,
        bool leaf = false,
        CancellationToken token = default
    )
    {
        StartingDirectory = startingDirectory;
        ParallelThreshold = parallelThreshold;
        StringValueSelector = stringValueSelector ?? DefaultStringValueSelector;
        Height = height;
        Width = width;
        NodeWidth = nodeWidth;
        Limit = limit;
        Token = token;
        RootNodeWidth = rootNodeWidth < 0
            ? nodeWidth
            : rootNodeWidth;
        OrderBy = orderBy;
        Descending = descending;
        Filter = filter ?? (_ => true);
        Leaf = leaf;
        if (!Leaf)
        {
            Within = within;
        }
 
        File = file;
        RemoveBranchesExceptFilteredImpl = new RemoveBranchesExceptFilteredImpl(
            filter: Filter,
            cancellationToken: Token
        );
 
        PrintNodeImpl = new PrintNodeImpl(
            orderer: CreateOrderer(),
            width: Width,
            stringValueSelector: StringValueSelector,
            token: Token
        );
 
        ClearChildrenExceedingWidthImpl = new ClearChildrenExceedingWidthImpl(
            nodeWidth: NodeWidth,
            rootNodeWidth: RootNodeWidth,
            parallelThreshold: ParallelThreshold
        );
 
        ClearLeavesFailingFilterImpl = new ClearLeavesFailingFilterImpl(filter: Filter);
 
        BfsImpl = CreateBfsImpl();
 
 
        return;
 
        FileSystemInfoTreeNodeEnumerableProcessor CreateOrderer()
        {
            FileSystemInfoTreeNodeEnumerableProcessor orderer = NodeOrderers.GetValueOrDefault(
                OrderBy, DefaultNodeOrderer
            );
 
            return Descending
                ? orderer.AndThen(x => x.Reverse())
                : orderer;
        }
 
        BfsExecutor<FileSystemInfo> CreateBfsImpl()
        {
            return new BfsExecutor<FileSystemInfo>
            {
                ChildProvider = ChildProvider,
                ShouldBreak = node =>
                {
                    Token.ThrowIfCancellationRequested();
                    return node.Height > Height || node.Count > Limit;
                },
                Where = CreateWhereFilter()
            };
 
            Func<FileSystemInfoTreeNode, bool> CreateWhereFilter()
            {
                if (Within || Leaf)
                {
                    return _ => true;
                }
 
                return node => Filter(node.Value);
            }
        }
    }
 
 
    public IReadOnlyList<FileSystemInfoTreeNode> CreateTreeNodes()
    {
        if (!StartingDirectory.Exists)
        {
            throw new DirectoryNotFoundException($"Directory not found: {StartingDirectory}");
        }
 
        if (RootNodeWidth == 0 || Limit <= 0 || Height <= 0)
        {
            return [];
        }
 
        var result = BfsImpl
           .Invoke(StartingDirectory)
           .ToList();
 
        if (Within)
        {
            RemoveBranchesExceptFilteredImpl.Invoke(result);
        }
        else if (Leaf)
        {
            ClearLeavesFailingFilterImpl.Invoke(result);
        }
 
        ClearChildrenExceedingWidthImpl.Invoke(result);
 
 
        return result;
    }
 
 
    public FileSystemInfoPrintNodeEnumerable CreatePrintNodes() =>
        CreateTreeNodes()
           .Take(1)
           .SelectMany(PrintNodeImpl.CreatePrintNodes);
 
 
    public int PredictMaxPrintNodeWidth()
    {
        var parameters = new[] { RootNodeWidth, NodeWidth, Width };
 
        var num = RootNodeWidth <= -1
            ? Math.Max(NodeWidth, Width)
            : parameters.Max();
 
        return Math.Max(1, num);
    }
 
    public int PredictMaxItemCount()
    {
        var parameters = new[]
        {
            Height,
            NodeWidth,
            RootNodeWidth,
            Width,
            Limit
        };
        return Math.Max(0, parameters.Min());
    }
 
 
    public string Invoke() =>
        CreatePrintNodes()
           .ToTreeString();
}
 
public partial class PrintTreeService
{
    public static string DefaultStringValueSelector(FileSystemInfoTreeNode node) => node.Value.Name;
 
    private static readonly Dictionary<string, FileSystemInfoTreeNodeEnumerableProcessor> NodeOrderers =
        new(StringComparer.OrdinalIgnoreCase)
        {
            ["Name"] = DefaultNodeOrderer,
            ["CreationTime"] = x => x.OrderBy(n => n.Value.CreationTime),
            ["LastAccessTime"] = x => x.OrderBy(n => n.Value.LastAccessTime),
            ["LastWriteTime"] = x => x.OrderBy(n => n.Value.LastWriteTime),
            ["Extension"] = x => x.OrderBy(n => n.Value.Extension),
            ["Attributes"] = x => x.OrderBy(n => n.Value.Attributes),
            ["Exists"] = x => x.OrderBy(n => n.Value.Exists),
            ["ChildCount"] = x => x.OrderBy(n => n.Children.Count)
        };
 
 
    public static DirectoryTreeNodeEnumerable DefaultNodeOrderer(DirectoryTreeNodeEnumerable node) => node;
}