diff --git a/README.md b/README.md index c0201ec..6b2b9c1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,137 @@ -```bash -dotnet run --project .\src\FileTree.CLI\FileTree.CLI.csproj -- -d 5 -f Unicode -e .cs .json -h +## FileTree.Core + +FileTree.Core is a .NET 8 library and CLI tool for generating directory trees with rich filtering, depth/width limits, and support for `.gitignore` rules. +It can be used both as a reusable library in your applications and as a command‑line tool for quickly inspecting project structures. + +## Features + +- **Multiple output formats**: `Ascii`, `Unicode`, `Markdown` +- **Respects `.gitignore`**: optionally skip ignored files and folders +- **Depth/width limits**: constrain recursion depth, per‑level width, and total node count +- **Flexible filtering**: include/exclude by extension or name, skip empty folders, hide hidden files +- **Library + CLI**: use `FileTreeService` in code or via `dotnet run` + +## Requirements + +- **.NET SDK**: 8.0 or later + +## Getting started + +Clone the repository and restore/build: + +```bash +dotnet restore +dotnet build ``` + +### Running the CLI + +From the repository root: + +```bash +dotnet run --project .\src\FileTree.CLI\FileTree.CLI.csproj -- -d 5 -f Unicode -e .cs,.json -h +``` + +This command: + +- **Scans** the current directory (no explicit path provided) +- **Limits depth** to 5 levels (`-d 5`) +- **Uses Unicode** tree characters (`-f Unicode`) +- **Excludes** `.cs` and `.json` files (`-e .cs,.json`) +- **Skips hidden** files and folders (`-h`) + +### Command‑line options + +All options are optional; sensible defaults are applied when they are omitted. + +| Option | Long name | Type | Description | +|--------|------------------|-----------|-------------| +| `path` | — | value | Path to the directory to scan. Defaults to the current directory. | +| `-d` | `--max-depth` | `int` | Maximum depth of the tree. `-1` (default) means unlimited. | +| `-w` | `--max-width` | `int` | Maximum number of siblings per level. `-1` (default) means unlimited. | +| `-n` | `--max-nodes` | `int` | Maximum total number of nodes in the tree. `-1` (default) means unlimited. | +| `-g` | `--use-gitignore`| `bool` | Use `.gitignore` rules to filter files. Defaults to `true` when omitted. | +| `-f` | `--format` | enum | Output format: `Ascii`, `Markdown`, or `Unicode`. Default is `Ascii`. | +| `-i` | `--include-ext` | list | Comma‑separated list of extensions to include, e.g. `-i .cs,.csproj`. | +| `-e` | `--exclude-ext` | list | Comma‑separated list of extensions to exclude, e.g. `-e .dll,.pdb`. | +| — | `--include-names`| list | Comma‑separated file/directory names to always include. | +| — | `--exclude-names`| list | Comma‑separated file/directory names to exclude. | +| — | `--ignore-empty` | flag | Skip empty folders. | +| `-h` | `--hidden` | flag | Exclude hidden files and folders. Defaults to `true` when omitted. | + +#### More examples + +Scan a specific folder with Markdown output: + +```bash +dotnet run --project .\src\FileTree.CLI\FileTree.CLI.csproj -- "C:\Projects\MyApp" -f Markdown +``` + +Include only C# sources and ignore empty directories: + +```bash +dotnet run --project .\src\FileTree.CLI\FileTree.CLI.csproj -- -i .cs -d 4 --ignore-empty +``` + +Disable `.gitignore` handling and show hidden files: + +```bash +dotnet run --project .\src\FileTree.CLI\FileTree.CLI.csproj -- -g false -h false +``` + +## Library usage + +You can use the core scanning and formatting functionality directly from C# via `FileTreeService` in `FileTree.Core`. + +```csharp +using FileTree.Core.Models; +using FileTree.Core.Services; + +var options = new FileTreeOptions +{ + MaxDepth = 5, + MaxWidth = -1, + MaxNodes = -1, + UseGitIgnore = true, + SkipHidden = true, + Format = OutputFormat.Unicode, + Filter = new FilterOptions + { + IncludeExtensions = new() { ".cs", ".csproj" }, + IgnoreEmptyFolders = true + } +}; + +FileTreeService service = new(); +string tree = service.Generate(@"C:\Projects\MyApp", options); + +Console.WriteLine(tree); +``` + +## Output formats + +- **Ascii**: Plain ASCII tree (`|--`, `+--`) +- **Unicode**: Box‑drawing characters for a nicer console tree +- **Markdown**: Tree formatted for embedding in Markdown documents + +The format is selected via `OutputFormat` in code or `-f/--format` in the CLI. + +## Development + +- **Run tests**: + +```bash +dotnet test +``` + +The main projects are: + +- `src/FileTree.Core` — core library (scanning, filtering, formatting) +- `src/FileTree.CLI` — command‑line interface +- `tests/FileTree.Core.Tests` — unit tests + +## License + +This project is licensed under the **Apache License 2.0**. +See the `LICENSE` file for full details. + diff --git a/src/FileTree.CLI/AppPaths.cs b/src/FileTree.CLI/AppPaths.cs index 87b6ec0..788df1c 100644 --- a/src/FileTree.CLI/AppPaths.cs +++ b/src/FileTree.CLI/AppPaths.cs @@ -2,9 +2,16 @@ namespace FileTree.CLI; +/// +/// Utility for determining FileTree executable and global configuration file paths. +/// Handles creation/reset/deletion/opening of FileTree.ignore.txt (filters) and FileTree.settings.txt (CLI defaults). +/// All paths relative to executable directory. +/// internal static class AppPaths { + /// Filename for global gitignore-style filter rules near executable. internal const string GlobalIgnoreFileName = "FileTree.ignore.txt"; + /// Filename for default CLI options/settings near executable. internal const string GlobalSettingsFileName = "FileTree.settings.txt"; private const string DefaultGlobalIgnoreContents = "# FileTree global ignore rules\n" + @@ -35,6 +42,11 @@ internal static string GetExecutablePath() return modulePath; } + /// + /// Gets directory containing the FileTree executable. + /// Prefers AppContext.BaseDirectory, falls back to parent of exe path. + /// + /// Normalized full directory path. internal static string GetExecutableDirectory() { if (!string.IsNullOrWhiteSpace(AppContext.BaseDirectory)) @@ -45,16 +57,26 @@ internal static string GetExecutableDirectory() return Path.GetDirectoryName(GetExecutablePath())!; } + /// Gets path to global FileTree.ignore.txt next to executable. + /// Full path (dir may not exist). internal static string GetGlobalIgnorePath() { return Path.Combine(GetExecutableDirectory(), GlobalIgnoreFileName); } + /// Gets path to global FileTree.settings.txt next to executable. + /// Full path (dir may not exist). internal static string GetGlobalSettingsPath() { return Path.Combine(GetExecutableDirectory(), GlobalSettingsFileName); } + /// + /// Ensures FileTree.ignore.txt exists (creates with defaults if missing). + /// + /// Output: full file path. + /// Output: null on success, else exception message. + /// true if exists or created, false on error. internal static bool TryEnsureGlobalIgnoreFileExists(out string path, out string? error) { path = GetGlobalIgnorePath(); @@ -77,6 +99,10 @@ internal static bool TryEnsureGlobalIgnoreFileExists(out string path, out string } } + /// Overwrites FileTree.ignore.txt with default content. + /// Output: full file path. + /// Output: null on success, else exception message. + /// true if written, false on error (file/dir perms). internal static bool TryResetGlobalIgnoreFile(out string path, out string? error) { path = GetGlobalIgnorePath(); @@ -94,6 +120,10 @@ internal static bool TryResetGlobalIgnoreFile(out string path, out string? error } } + /// Ensures FileTree.settings.txt exists (creates with defaults if missing). + /// Output: full file path. + /// Output: null on success, else exception message. + /// true if exists or created, false on error. internal static bool TryEnsureGlobalSettingsFileExists(out string path, out string? error) { path = GetGlobalSettingsPath(); @@ -116,6 +146,10 @@ internal static bool TryEnsureGlobalSettingsFileExists(out string path, out stri } } + /// Overwrites FileTree.settings.txt with default content. + /// Output: full file path. + /// Output: null on success, else exception message. + /// true if written, false on error (perms). internal static bool TryResetGlobalSettingsFile(out string path, out string? error) { path = GetGlobalSettingsPath(); @@ -133,6 +167,10 @@ internal static bool TryResetGlobalSettingsFile(out string path, out string? err } } + /// Deletes FileTree.ignore.txt if exists. + /// Output: attempted file path. + /// Output: null on success, else exception. + /// true if absent or deleted, false on error. internal static bool TryDeleteGlobalIgnoreFile(out string path, out string? error) { path = GetGlobalIgnorePath(); @@ -154,6 +192,10 @@ internal static bool TryDeleteGlobalIgnoreFile(out string path, out string? erro } } + /// Deletes FileTree.settings.txt if exists. + /// Output: attempted file path. + /// Output: null on success, else exception. + /// true if absent or deleted, false on error. internal static bool TryDeleteGlobalSettingsFile(out string path, out string? error) { path = GetGlobalSettingsPath(); @@ -175,6 +217,10 @@ internal static bool TryDeleteGlobalSettingsFile(out string path, out string? er } } + /// Opens FileTree.settings.txt in default editor (creates if missing). + /// Output: full file path. + /// Output: null on success/open, else exception. + /// true if opened (or created+opened), false on error. internal static bool TryOpenGlobalSettingsFile(out string path, out string? error) { path = GetGlobalSettingsPath(); @@ -203,6 +249,10 @@ internal static bool TryOpenGlobalSettingsFile(out string path, out string? erro } } + /// Opens FileTree.ignore.txt in default editor (creates if missing). + /// Output: full file path. + /// Output: null on success/open, else exception. + /// true if opened (or created+opened), false on error. internal static bool TryOpenGlobalIgnoreFile(out string path, out string? error) { path = GetGlobalIgnorePath(); diff --git a/src/FileTree.CLI/CommandLineOptions.cs b/src/FileTree.CLI/CommandLineOptions.cs index 37b3d4b..5eaf4ba 100644 --- a/src/FileTree.CLI/CommandLineOptions.cs +++ b/src/FileTree.CLI/CommandLineOptions.cs @@ -4,62 +4,84 @@ namespace FileTree.CLI; [Verb("scan", isDefault: true, HelpText = "Scan and print the directory tree.")] +/// +/// Options for the default 'scan' command that generates and prints a visual directory tree. +/// Supports tree limits, formatting, filtering (gitignore-style or legacy), hidden files, and output behaviors. +/// Example: filetree . --max-depth 3 --format Markdown --filter-rules '*.log,bin/' +/// public class ScanCommandOptions { + /// Target directory path (positional argument). Defaults to current directory if omitted. [Value(0, MetaName = "path", HelpText = "Path to the directory to scan.", Required = false)] public string? Path { get; set; } + /// Alternative path specification via short/long option. Overrides positional if both provided. [Option('p', "path", HelpText = "Path to the directory to scan.", Required = false)] public string? PathOption { get; set; } + /// Limit tree recursion depth. -1 = unlimited (default). [Option('d', "max-depth", HelpText = "Maximum depth of the tree.", Required = false)] public int? MaxDepth { get; set; } + /// Limit siblings per directory. -1 = unlimited (default). Helps wide dirs. [Option('w', "max-width", HelpText = "Maximum width (number of siblings) per level.", Required = false)] public int? MaxWidth { get; set; } + /// Global cap on tree nodes. -1 = unlimited (default). Prevents huge outputs. [Option('n', "max-nodes", HelpText = "Maximum total number of nodes in the tree.", Required = false)] public int? MaxNodes { get; set; } + /// Apply .gitignore/.git/info/exclude rules from repo root. Default: true if .git found. [Option('g', "use-gitignore", HelpText = "Use .gitignore rules to filter files (true|false).", Required = false)] public bool? UseGitIgnore { get; set; } + /// Tree rendering style. Default: . [Option('f', "format", HelpText = "Output format (Ascii, Markdown, Unicode).", Required = false)] public OutputFormat? Format { get; set; } + /// Collapse dirs with > N children. Null = auto based on width/depth. [Option("collapse-threshold", HelpText = "Collapse directories when item count exceeds this value.", Required = false)] public int? CollapseThreshold { get; set; } + /// Show first N children before placeholder. Default: 1. [Option("collapse-keep-start", HelpText = "Number of items to keep at the start when collapsing.", Required = false)] public int? CollapseKeepStart { get; set; } + /// Show last N children after placeholder. Default: 1. [Option("collapse-keep-end", HelpText = "Number of items to keep at the end when collapsing.", Required = false)] public int? CollapseKeepEnd { get; set; } + /// Placeholder representation. Default: . [Option("collapse-style", HelpText = "Collapse placeholder style (Simple, Count, ByExtension).", Required = false)] public CollapseStyle? CollapseStyle { get; set; } + /// Min depth for collapsing (1=root children). Default: 1. [Option("collapse-from", HelpText = "Start collapsing only from this depth (1 = first level under root).", Required = false)] public int? CollapseFrom { get; set; } - // ============================================================================ - // New gitignore-style filtering options (recommended) - // ============================================================================ - + /// + /// + /// Recommended modern filtering with gitignore syntax. + /// Precedence: inline > local/global files > gitignore. + /// + /// [Option("filter-rules", HelpText = "Filter rules in gitignore format (comma-separated). Example: '*.log,bin/,!important.txt'", Required = false, Separator = ',')] + /// Inline gitignore-style patterns. !negates. Multiple via comma or repeat. public IEnumerable? FilterRules { get; set; } + /// Path to .filetreeignore in target dir or subdir. Auto-searched if omitted. [Option("filter-file", HelpText = "Path to local filter configuration file with gitignore-style rules.", Required = false)] public string? FilterFile { get; set; } + /// Override app default/global .filetreeignore (near exe). Multiple levels supported. [Option("global-filter-file", HelpText = "Path to global filter configuration file with gitignore-style rules.", Required = false)] public string? GlobalFilterFile { get; set; } @@ -69,11 +91,13 @@ public class ScanCommandOptions Required = false)] public bool? NoDefaultFilters { get; set; } + /// Skip FileTree.ignore near executable. Short: '!'. [Option('!', "no-app-global-ignore", HelpText = "Don't load app-level global filter file (FileTree.ignore near the executable) (true|false).", Required = false)] public bool? NoAppGlobalIgnore { get; set; } + /// Disable recursive .filetreeignore loading. Default: load. [Option("no-local-filters", HelpText = "Don't load local .filetreeignore files from the directory tree (true|false).", Required = false)] public bool? NoLocalFilters { get; set; } @@ -82,10 +106,12 @@ public class ScanCommandOptions Required = false)] public bool? NoDefaultSettings { get; set; } + /// Legacy filtering - use instead for gitignore syntax. // ============================================================================ // Legacy filtering options (deprecated but maintained for backward compatibility) // ============================================================================ + /// Only show files with these extensions (e.g. 'cs,js'). DEPRECATED. [Option('i', "include-ext", HelpText = "[DEPRECATED] Include only these extensions (comma-separated). Use --filter-rules instead.", Required = false, Separator = ',')] @@ -109,52 +135,76 @@ public class ScanCommandOptions [Option("ignore-empty", HelpText = "Skip empty folders (true|false).", Required = false)] public bool? IgnoreEmptyFolders { get; set; } + /// Hide hidden files/dirs (Unix dotfiles, Windows hidden attr). Default: true. Short: 'h'. [Option('h', "hidden", HelpText = "Exclude hidden files and folders (true|false).", Required = false)] public bool? SkipHidden { get; set; } + /// Style hidden nodes (if not skipped). Default: true. [Option("highlight-hidden", HelpText = "Highlight hidden files and folders in output (true|false).", Required = false)] public bool? HighlightHiddenFiles { get; set; } + /// Marker for hidden (Prefix/Suffix/Minimal). Default: Suffix. [Option("hidden-style", HelpText = "Hidden file style (Prefix, Suffix, Minimal).", Required = false)] public HiddenStyle? HiddenStyle { get; set; } + /// Interactive mode: accept more options until 'show'. [Option("wait", HelpText = "Do not run immediately; enter interactive mode, accept more options, and run on 'show' (true|false).", Required = false)] public bool? Wait { get; set; } + /// Copy tree to clipboard after generation. Short: 'c'. [Option('c', "copy", HelpText = "Copy output to clipboard")] public bool Copy { get; set; } + /// Suppress console output (use with --copy). Short: 's'. [Option('s', "silent", HelpText = "Do not print output to console")] public bool Silent { get; set; } + /// Print summary options before tree. [Option("show-options", HelpText = "Print core scan options before output")] public bool ShowOptions { get; set; } + /// Print verbose all-options before tree. [Option("show-options-all", HelpText = "Print all scan options before output")] public bool ShowOptionsAll { get; set; } } [Verb("install", HelpText = "Install FileTree into the system (PATH, context menu, etc.).")] +/// +/// Options for the 'install' command. Installs FileTree to user PATH, creates shims/aliases, adds Windows context menus, ensures global config files. +/// Platform-specific: Full on Windows, instructions on Linux. +/// public class InstallCommandOptions { } [Verb("uninstall", HelpText = "Uninstall FileTree from the system for the current user.")] +/// +/// Options for the 'uninstall' command. Removes user-specific installation (PATH, shims, aliases, context menus, config files). +/// public class UninstallCommandOptions { } [Verb("uninstall-deep", HelpText = "Deep uninstall FileTree (search and remove all FileTree entries for the current user).")] +/// +/// Options for the 'uninstall-deep' command. Scans and prompts removal of all FileTree entries (PATH/shims/aliases/context menus). +/// Safer than uninstall for multiple/unknown installs. +/// public class UninstallDeepCommandOptions { } [Verb("paths", HelpText = "Show the executable location and global filter file path.")] +/// +/// Options for the 'paths' command. Displays executable location, directory, and global config file paths. +/// Useful for scripting/config verification. +/// Example output: Executable: C:\path\to\FileTree.exe, Global ignore: ...\FileTree.ignore.txt +/// public class PathsCommandOptions { } diff --git a/src/FileTree.CLI/Program.cs b/src/FileTree.CLI/Program.cs index dd03222..286e1a9 100644 --- a/src/FileTree.CLI/Program.cs +++ b/src/FileTree.CLI/Program.cs @@ -7,11 +7,22 @@ namespace FileTree.CLI; +/// +/// Main entry point for FileTree CLI application. +/// Handles argument parsing, global options (--pause-exit), config commands (config rules/settings), scan/install/uninstall. +/// Uses CommandLineParser, supports interactive mode, settings from FileTree.settings.txt, platform install via SystemIntegrator. +/// internal class Program { private const string PauseExitLongOption = "pause-exit"; private const char PauseExitShortOption = 'k'; + /// + /// Entry point. Processes args, handles globals/config/help, dispatches to scan/install/uninstall/paths. + /// Applies settings, normalizes booleans, pauses if --pause-exit/'-k'. + /// + /// CLI arguments. + /// 0 success, 1 error/parse fail. private static int Main(string[] args) { var argsList = args.ToList(); @@ -57,6 +68,9 @@ private static int Main(string[] args) return res; } + /// Orchestrates scan: CLI to state, apply defaults, dispatch interactive/once. + /// Parsed scan options. + /// 0 success, 1 error. private static int RunScan(ScanCommandOptions opts) { var state = ScanOptionsState.FromCli(opts); @@ -70,6 +84,9 @@ private static int RunScan(ScanCommandOptions opts) return RunScanOnce(state); } + /// Executes single tree scan/print/copy. Handles validation/output/errors. + /// Resolved scan state/options. + /// 0 success, 1 validation error. private static int RunScanOnce(ScanOptionsState state) { var targetPath = state.GetTargetPath(); @@ -120,6 +137,9 @@ private static int RunScanOnce(ScanOptionsState state) } } + /// Interactive shell for scan options. Parses input until 'show'/'exit'. + /// Initial scan state, merged with interactive inputs. + /// 0 success/exit, 1 error. private static int RunScanInteractive(ScanOptionsState state) { var current = state; @@ -474,6 +494,10 @@ private static int RunPaths() return 0; } + /// Handles 'config rules/settings' subcommands (open/reset/path). + /// Remaining args after 'config'. + /// Output: 0 success, 1 invalid. + /// true if handled, false otherwise. private static bool TryHandleConfigCommand(string[] args, out int exitCode) { exitCode = 0; @@ -636,6 +660,8 @@ private static void ApplySettingsDefaultsIfNeeded(ScanOptionsState state) state.ApplyDefaults(defaults); } + /// Loads defaults from FileTree.settings.txt, parses as CLI args, converts to state. + /// Parsed state or null if missing/unparseable. private static ScanOptionsState? LoadSettingsDefaults() { var path = AppPaths.GetGlobalSettingsPath(); @@ -696,6 +722,8 @@ private static void ApplySettingsDefaultsIfNeeded(ScanOptionsState state) } } + /// Delegates to platform integrator for install. + /// 0 success. private static async Task RunInstallAsync() { var integrator = SystemIntegratorFactory.Create(); @@ -703,6 +731,8 @@ private static async Task RunInstallAsync() return 0; } + /// Delegates to platform integrator for user uninstall. + /// 0 success. private static async Task RunUninstallAsync() { var integrator = SystemIntegratorFactory.Create(); @@ -710,6 +740,8 @@ private static async Task RunUninstallAsync() return 0; } + /// Delegates to platform integrator for deep uninstall. + /// 0 success. private static async Task RunUninstallDeepAsync() { var integrator = SystemIntegratorFactory.Create(); diff --git a/src/FileTree.CLI/ScanOptionsState.cs b/src/FileTree.CLI/ScanOptionsState.cs index 4aff9eb..a708b03 100644 --- a/src/FileTree.CLI/ScanOptionsState.cs +++ b/src/FileTree.CLI/ScanOptionsState.cs @@ -3,6 +3,10 @@ namespace FileTree.CLI; +/// +/// Mirrors + conversion/merge/defaults to . +/// Handles path prioritization, legacy/modern filter mapping. +/// internal sealed class ScanOptionsState { public string? Path { get; set; } @@ -38,6 +42,10 @@ internal sealed class ScanOptionsState public bool? ShowOptions { get; set; } public bool? ShowOptionsAll { get; set; } + /// Creates state from parsed CLI options, normalizes paths. + /// Raw CLI options. + /// New state instance. + /// cli null. public static ScanOptionsState FromCli(ScanCommandOptions cli) { if (cli == null) @@ -89,6 +97,8 @@ public static ScanOptionsState FromCli(ScanCommandOptions cli) return state; } + /// Merges non-null values from source (path prio: PathOption > Path, overrides hasValue). + /// Options to merge in. public void MergeFrom(ScanOptionsState source) { if (source == null) @@ -139,6 +149,8 @@ public void MergeFrom(ScanOptionsState source) if (source.ShowOptionsAll.HasValue) ShowOptionsAll = source.ShowOptionsAll; } + /// Applies defaults only to null/unset fields (path prio: PathOption > Path). + /// Defaults to apply. public void ApplyDefaults(ScanOptionsState defaults) { if (defaults == null) @@ -188,6 +200,8 @@ public void ApplyDefaults(ScanOptionsState defaults) if (!ShowOptionsAll.HasValue && defaults.ShowOptionsAll.HasValue) ShowOptionsAll = defaults.ShowOptionsAll; } + /// Converts to core , applies defaults (-1 unlimited, etc.), builds RulesSource. + /// Ready core options. public FileTreeOptions ToFileTreeOptions() { return new FileTreeOptions diff --git a/src/FileTree.CLI/SystemIntegrator/ISystemIntegrator.cs b/src/FileTree.CLI/SystemIntegrator/ISystemIntegrator.cs index c269c7c..ec6045d 100644 --- a/src/FileTree.CLI/SystemIntegrator/ISystemIntegrator.cs +++ b/src/FileTree.CLI/SystemIntegrator/ISystemIntegrator.cs @@ -1,8 +1,15 @@ namespace FileTree.CLI.SystemIntegrator; +/// +/// Platform-specific system integration (PATH, shims, aliases, context menus). +/// Windows: full auto. Linux: prints manual instructions. Other: NoOp. +/// public interface ISystemIntegrator { + /// Adds FileTree to user PATH/context menu/shims/configs. Task InstallAsync(); + /// Removes user install (known locations). Task UninstallAsync(); + /// Scans/prompts removal of all FileTree entries (unknown installs). Task UninstallDeepAsync(); } diff --git a/src/FileTree.CLI/SystemIntegrator/LinuxSystemIntegrator.cs b/src/FileTree.CLI/SystemIntegrator/LinuxSystemIntegrator.cs index 5b2f445..e908a54 100644 --- a/src/FileTree.CLI/SystemIntegrator/LinuxSystemIntegrator.cs +++ b/src/FileTree.CLI/SystemIntegrator/LinuxSystemIntegrator.cs @@ -3,8 +3,13 @@ namespace FileTree.CLI.SystemIntegrator; +/// +/// Linux integrator: prints manual PATH/~.local/bin instructions, handles configs. +/// No auto-modification (user perms). +/// internal sealed class LinuxSystemIntegrator : ISystemIntegrator { + /// public Task InstallAsync() { var exePath = GetExecutablePath(); @@ -61,6 +66,7 @@ public Task InstallAsync() return Task.CompletedTask; } + /// public Task UninstallAsync() { var ignorePath = AppPaths.GetGlobalIgnorePath(); @@ -94,6 +100,7 @@ public Task UninstallAsync() return Task.CompletedTask; } + /// public Task UninstallDeepAsync() { var ignorePath = AppPaths.GetGlobalIgnorePath(); diff --git a/src/FileTree.CLI/SystemIntegrator/NoOpSystemIntegrator.cs b/src/FileTree.CLI/SystemIntegrator/NoOpSystemIntegrator.cs index f47da27..d1e8195 100644 --- a/src/FileTree.CLI/SystemIntegrator/NoOpSystemIntegrator.cs +++ b/src/FileTree.CLI/SystemIntegrator/NoOpSystemIntegrator.cs @@ -1,5 +1,9 @@ namespace FileTree.CLI.SystemIntegrator; +/// +/// No-operation integrator for unsupported OS. +/// Prints "unsupported" message and completes. +/// internal sealed class NoOpSystemIntegrator : ISystemIntegrator { public Task InstallAsync() diff --git a/src/FileTree.CLI/SystemIntegrator/SystemIntegratorFactory.cs b/src/FileTree.CLI/SystemIntegrator/SystemIntegratorFactory.cs index 872c490..ea6939b 100644 --- a/src/FileTree.CLI/SystemIntegrator/SystemIntegratorFactory.cs +++ b/src/FileTree.CLI/SystemIntegrator/SystemIntegratorFactory.cs @@ -2,8 +2,14 @@ namespace FileTree.CLI.SystemIntegrator; +/// +/// Creates platform-appropriate . +/// Windows/Linux/NoOp based on RuntimeInformation.IsOSPlatform. +/// internal static class SystemIntegratorFactory { + /// Detects OS and instantiates integrator. + /// WindowsSystemIntegrator on Windows, Linux on Linux, NoOp otherwise. public static ISystemIntegrator Create() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/src/FileTree.CLI/SystemIntegrator/WindowsSystemIntegrator.cs b/src/FileTree.CLI/SystemIntegrator/WindowsSystemIntegrator.cs index 9152ec0..c320bbf 100644 --- a/src/FileTree.CLI/SystemIntegrator/WindowsSystemIntegrator.cs +++ b/src/FileTree.CLI/SystemIntegrator/WindowsSystemIntegrator.cs @@ -7,6 +7,10 @@ namespace FileTree.CLI.SystemIntegrator; [SupportedOSPlatform("windows")] +/// +/// Windows-specific implementation: adds to user PATH, BAT/PowerShell shims/aliases, registry context menus (dirs/files/desktop/background, default/custom). +/// Deep uninstall prompts/scans for entries. Broadcasts env changes. +/// internal sealed class WindowsSystemIntegrator : ISystemIntegrator { private const string DirectoryMenuKey = @@ -33,6 +37,7 @@ internal sealed class WindowsSystemIntegrator : ISystemIntegrator private const string DirectoryBackgroundMenuKeyCustom = @"Software\Classes\Directory\Background\shell\FileTreeCustom"; + /// public Task InstallAsync() { var exePath = GetExecutablePath(); @@ -92,6 +97,7 @@ public Task InstallAsync() return Task.CompletedTask; } + /// public Task UninstallAsync() { var exePath = GetExecutablePath(); @@ -154,6 +160,7 @@ public Task UninstallAsync() return Task.CompletedTask; } + /// public Task UninstallDeepAsync() { var exePath = GetExecutablePath(); diff --git a/src/FileTree.Core/Abstractions/IFileTreeService.cs b/src/FileTree.Core/Abstractions/IFileTreeService.cs index b7b1b01..f6c766c 100644 --- a/src/FileTree.Core/Abstractions/IFileTreeService.cs +++ b/src/FileTree.Core/Abstractions/IFileTreeService.cs @@ -2,7 +2,16 @@ namespace FileTree.Core.Abstractions; +/// +/// Core tree generation service interface. +/// public interface IFileTreeService { + /// Main entrypoint: Generates formatted tree string. + /// Root dir path (validated). + /// Scan/format config. + /// Formatted tree. + /// Invalid/inaccessible path. + /// Scan access denied. string Generate(string rootPath, FileTreeOptions options); } diff --git a/src/FileTree.Core/FileTree.Core.xml b/src/FileTree.Core/FileTree.Core.xml new file mode 100644 index 0000000..2566015 --- /dev/null +++ b/src/FileTree.Core/FileTree.Core.xml @@ -0,0 +1,745 @@ + + + + FileTree.Core + + + + + Core tree generation service interface. + + + + Main entrypoint: Generates formatted tree string. + Root dir path (validated). + Scan/format config. + Formatted tree. + Invalid/inaccessible path. + Scan access denied. + + + + Legacy file filter implementation using simple list-based filtering. + + + This class is obsolete and maintained only for backward compatibility. + Use FilterRulesSource with gitignore-style rules instead. + This class will be removed in v2.0.0. + + + + Initializes with filter options lists. + Lists of include/exclude. + + + Include if in includeNames or not in excludes; checks extension if IncludeExtensions set. + + + Include if in includeNames or not in excludeNames (no extension logic). + + + + Context for filter operations: combines with optional gitignore rules. + Passed to rule loaders/evaluators. + + + + Initializes with options and optional gitignore. + + + Filter options from user input. + + + Loaded .gitignore rules if applicable. + + + + Loads filtering rules from multiple sources and combines them into a single ordered list. + Rules are applied in order of precedence: App global -> Default global -> Custom global -> Local config -> Inline rules. + + + Loads filter rules from various sources (app/global/local configs, inline) in precedence order. + Parses .filetreeignore files, combines into ordered list for gitignore matching. + Cross-platform home dir detection. + + + + + Loads and combines filtering rules from all configured sources. + + Configuration specifying which rule sources to load. + A list of combined rules in the correct order. + Loads rules from all sources in order, adding to list. + + + + Gets the default global configuration file path based on the operating system. + + Path to default global config, or null if home directory cannot be determined. + + + + Gets the user's home directory path in a cross-platform manner. + + User's home directory path, or null if it cannot be determined. + + + + Loads rules from a file and adds them to the provided GitIgnoreRules instance. + + The rules list to add rules to. + Path to the file containing rules. + Thrown if the specified file does not exist. + Thrown if an error occurs while reading the file. + + + + Legacy filtering interface for file/dir inclusion decisions. + Used for backward compatibility with list-based filters. + New code should use gitignore-style FilterRulesSource. + + + + Decides if file should be included based on name/path lists. + + + Decides if directory should be included based on name/path lists. + + + + Converts legacy FilterOptions properties to gitignore-style filtering rules. + This ensures backward compatibility with code using the old filtering API. + + + Converts legacy list-based FilterOptions to gitignore-style rules for compatibility. + Generates patterns like "**/*.ext" or "!**/*name". + + + + + Converts legacy filter options to gitignore-style rules. + + The FilterOptions containing legacy filter properties. + A list of gitignore-style rule strings. + + Conversion rules: + - ExcludeExtensions: Converts to "**/*{ext}" patterns + - ExcludeNames: Converts to "{name}" patterns + - IncludeExtensions: Converts to "*" (exclude all) followed by "!**/*{ext}" (except these) + - IncludeNames: Converts to "!{name}" (negation patterns) + + Note: The order matters in gitignore. Include rules must come after exclude rules + to properly negate them. + + Main conversion: lists to gitignore patterns. + + + + Checks if the legacy filter properties contain any filtering rules. + + The FilterOptions to check. + True if any legacy filter properties are configured, false otherwise. + + + + Normalizes a file extension by ensuring it starts with a dot. + + The extension to normalize (e.g., "txt" or ".txt"). + Normalized extension starting with a dot (e.g., ".txt"). + + + + ASCII art tree formatter using | / \ - characters. + Compatible with all terminals. + + + + Formats tree using ASCII characters. Root printed first, then children with connectors. + + + Formats single node name, handling collapsed placeholders and hidden styling. + + + + Formats placeholder strings for collapsed nodes based on : Simple "...", Count, ByExtension. + Called by formatters for IsCollapsedPlaceholder nodes. + + + + Main entry: Builds text, adds Markdown backticks if needed. + Collapsed node with counts. + Context for format-specific wrapping. + Placeholder string. + + + Builds core placeholder text based on style/counts/extension hint. + Node stats. + Collapse style/options. + Text like "... (3 files)" or "... (12 .cs)". + + + + Immutable context passed to formatters, providing access to . + Enables format-specific options without passing options directly. + + + + Creates context from options. + Formatting options. + + + Original options. + + + Shortcut to . + + + + Contract for formatting a tree into string representation. + Supports different output styles (ASCII, Unicode, Markdown). + + + + + Formats the tree hierarchy into a string. + + Root node of the tree. + Formatting context (options, styles). + Formatted tree string ready for output. + + + + Markdown list-style tree formatter. + Indented bullet-less list with / for dirs. + Ideal for GitHub README. + + + + Formats tree as indented Markdown list. Recursive write. + + + Recursively writes node at depth, with 2-space indent. + Current node. + Builder to append to. + Indent level. + Formatting context. + + + Formats node name: dir / suffix, hidden styling, collapsed placeholders. + + + + Utility for styling node names: applies hidden file markers (prefix/suffix/minimal) based on options. + Used by all tree formatters. + + + + Applies hidden styling if enabled and node is hidden. + Base name. + Node metadata. + Context for style. + Styled name. + + + Applies the configured hidden style to name. + + + + Factory for selecting concrete implementations based on . + Simple switch, extensible for new formats. + + + + Creates formatter for the given format. + Desired output format. + Appropriate formatter instance. + Unknown format. + + + + Unicode box-drawing tree formatter (│ ├ └ ─). Pretty terminal output. + Similar structure to Ascii, different chars. + + + + Formats tree using Unicode box characters. Root first, then children. + + + Formats node name, delegating to placeholder/styler. + + + + Factory methods for from empty, lines, or .gitignore file. + Handles parsing, comment stripping, trimming. + + + + Creates empty rules instance. + + + Parses lines, ignores empty/comments, adds to new rules. + + + Loads from file path, reads lines then FromLines. + + + + Wrapper around Ignore library for gitignore-style path matching. + Fluent Add rules, query IsIgnored/Filter. + + + + Initializes empty matcher. + + + Adds single rule, fluent. + Pattern. + + + Adds rules enumerable, cleans empty/comments, fluent. + + + Filters paths not ignored. + + + Tests if path ignored. + + + ... placeholder. + + + ... (5 more) with count. + + + ... [.cs x12] by extension. + + + + Represents a node in the file tree. + Leaf for files, container for dirs. Supports collapse placeholders. + Immutable except children/collapsed props. + + + + Constructor. + Basename. + Absolute path. + Dir or file. + Hidden flag. + + + Basename (no path). + + + Absolute path. + + + True if directory. + + + True if hidden. + + + Child nodes (read-only). + + + True if placeholder for collapsed children. + + + Total hidden children count. + + + Hidden file children count. + + + Hidden folder children count. + + + Extension hint for ByExtension collapse. + + + Adds child (during scan). + Child node. + + + Removes child (post-filter). + Child to remove. + + + + Core configuration for tree generation. + Controls scanning limits, filtering, formatting, collapse. + Defaults favor reasonable output; -1/-1 unlimited. + + + + Max recursion depth. -1 unlimited (default). + + + Max siblings per dir. -1 unlimited (default). + + + Global node cap. -1 unlimited (default). + + + Load/apply .gitignore rules from root. Default: false. + + + Exclude hidden files/dirs. Default: false. + + + Special style for hidden (if not skipped). Default: true. + + + Hidden marker style. Default: Prefix. + + + Tree render style (Ascii/Markdown/Unicode). Default: Ascii. + + + Filtering config (rules, legacy). + + + Collapse dirs > N children. Null=auto. + + + First N children to show in collapsed. Default: 1. + + + Last N children to show in collapsed. Default: 1. + + + Placeholder style. Default: Count. + + + Start collapsing from this depth (1=root children). Default: 1. + + + + Filtering configuration. + Modern: RulesSource gitignore-style. Legacy: ext/name lists (deprecated). + + + + + Source configuration for gitignore-style filtering rules. + This is the recommended way to configure filtering. + + + + + Skip folders that have no children after filtering is applied. + + + + + Whether to load local .filetreeignore files during scanning. + + + + + Include only these extensions if list is not empty. + + + + + Extensions to always exclude. + + + + + File/directory names to include always. + + + + + File/directory names to exclude. + + + + + Defines the sources from which filtering rules should be loaded. + Rules are applied in order: Global config -> Local config -> Inline rules, + with later rules taking precedence over earlier ones. + + + + + Path to the app-level global filter configuration file (e.g., FileTree.ignore in the app directory). + If null, no app-level global config will be loaded. + + + + + Whether to load the app-level global configuration file. + + + + + Filtering rules provided directly (e.g., from CLI arguments). + These rules use gitignore-style syntax. + + + + + Path to a global filter configuration file. + If null, only the default global config will be used (if enabled). + + + + + Path to a local filter configuration file (e.g., .filetreeignore in the working directory). + If null, no local config file will be loaded. + + + + + Whether to load the default global configuration file from the user's home directory. + Default location: ~/.filetreeignore (Linux/Mac) or %USERPROFILE%\.filetreeignore (Windows) + + + + + Style for marking hidden nodes. + + + + [hidden] prefix. + + + (hidden) suffix. + + + * minimal marker. + + + + Tree rendering format. + + + + ASCII box chars (| / \ -). + + + Markdown tree list. + + + Unicode tree chars (│ ├ └ ─). + + + + Default implementing tree collapse logic. + Recursively clones tree, replacing large dirs with placeholders. + Supports KeepStart/KeepEnd, from depth, different styles. + + + + Processes tree if threshold set, using recursive clone/collapse. + + + + Post-scan tree processor pipeline interface. + Transforms tree (e.g., collapse nodes, prune, etc.) based on options. + + + + Processes/transforms the tree root. + Input tree. + Processing options (threshold etc.). + Processed tree (may mutate/replace nodes). + + + + Implements tree scanning with limits, filtering, gitignore. + Recursive, depth/node aware. + + + Default implementation of . Handles recursive directory scanning with support for + depth/width/node limits, .gitignore rules, custom filters (legacy/new), hidden files, empty folder pruning. + Uses stack-based Enter/Exit for local .filetreeignore inheritance. + + + + Total nodes scanned (for MaxNodes limit). + + + Whether to prune empty folders post-scan. + + + Evaluator for inclusion rules (limits, filters, gitignore). + + + + Scans the directory tree starting from the specified root path. + + Validated full path to the root directory (caller must validate existence). + Scanning options. + FileNode representing the scanned tree. + + + + Recursively scans directory contents, applying inclusion rules and limits. + Builds child FileNode, recurses dirs, prunes empty if configured. + + Current directory. + Parent node to add children to. + Recursion depth. + Scan options. + + + + Cross-platform detector for hidden files and directories. + On Windows, checks the Hidden file attribute. + On Unix-like systems, checks for dot-prefix in name (e.g., .git). + + + + + Determines if the specified file system item is hidden. + + The file or directory information to check. + + true if the item is hidden; otherwise, false. + + + Platform-specific logic: + - Windows: Checks . + - Other OS: Checks if name starts with '.'. + + + + + Defines the scanning contract for building file tree structures from directories. + Implementations handle recursion, limits, filtering during scan. + Note: Named IFileScanner internally, but file is IFileTreeScanner.cs. + + + + + Scans the directory tree starting from , building a hierarchy. + Applies scanning limits, hidden/gitignore checks based on . + + Absolute path to root directory. + Scan configuration (depth, width, filters). + Root representing the tree. + Access denied during scan. + + + + Evaluates whether file system items should be included in the scan based on limits, filters, gitignore rules, hidden status, etc. + Supports hierarchical .filetreeignore files and legacy filter conversion. + Uses stack for local rules scope. + + + + Name of local ignore file for directory-specific rules. + + + + Creates a new instance of ScanInclusionEvaluator. + + The root directory path being scanned. + File tree scanning options. + Optional .gitignore rules to apply. + Optional custom filter rules to apply. + Initializes evaluator with root path, options, and rules sources. + Combines gitignore, custom rules, legacy filters. Builds initial filter rules. + + + + Determines whether a file or directory should be included in the scan results. + + The file or directory to evaluate. + Current depth in the directory tree. + Current number of nodes scanned. + True if the item should be included, false otherwise. + + + + Loads .filetreeignore from dir if exists, pushes to stack, rebuilds rules. + + Directory to check for local ignore. + True if rules loaded/pushed. + + + Pops local rules from stack if previously pushed, rebuilds effective rules. + True if EnterDirectory loaded rules. + + + + Main service orchestrating tree generation: scan -> process -> format. + DI-friendly ctor for testing. + + + + DI ctor. + Tree scanner. + Formatter selector. + Optional processors (default collapse). + + + Default ctor with built-in scanner/factory/collapse. + + + + Generates a formatted file tree representation for the specified directory. + + Path to the root directory to scan. Must be a valid, accessible directory. + Scanning and formatting options. + Formatted tree string. + Thrown if rootPath is invalid, not a directory, or inaccessible. + Thrown if scanning encounters permission issues inside dirs. + + + + Represents different types of path validation errors. + + + + + The path has invalid characters or format. + + + + + The path does not exist. + + + + + The path exists but is a file, not a directory. + + + + + No permission to access the directory. + + + + + Exception thrown when directory path validation fails. + Provides detailed error type and path for user-friendly messages. + + + + + Gets the error type. + + + + + Gets the full normalized path that failed validation. + + + + + Initializes a new instance with error type and path. + + + + + Initializes with inner exception (e.g. ArgumentException from Path.GetFullPath). + + + + diff --git a/src/FileTree.Core/Filtering/FileFilter.cs b/src/FileTree.Core/Filtering/FileFilter.cs index e25a218..0c4d921 100644 --- a/src/FileTree.Core/Filtering/FileFilter.cs +++ b/src/FileTree.Core/Filtering/FileFilter.cs @@ -12,15 +12,22 @@ namespace FileTree.Core.Filtering; /// This class will be removed in v2.0.0. /// [Obsolete("Use FilterRulesSource with gitignore-style rules instead. This class will be removed in v2.0.0")] +/// +/// Default implementation of . Matches against include/exclude lists for names/extensions. +/// Backward-compatible legacy filter. +/// internal class FileFilter : IFileFilter { private readonly FilterOptions _options; + /// Initializes with filter options lists. + /// Lists of include/exclude. public FileFilter(FilterOptions options) { _options = options; } + /// Include if in includeNames or not in excludes; checks extension if IncludeExtensions set. public bool ShouldIncludeFile(string fileName, string fullPath) { if (_options.IncludeNames.Contains(fileName, StringComparer.OrdinalIgnoreCase)) @@ -40,6 +47,7 @@ public bool ShouldIncludeFile(string fileName, string fullPath) return true; } + /// Include if in includeNames or not in excludeNames (no extension logic). public bool ShouldIncludeDirectory(string directoryName, string fullPath) { if (_options.IncludeNames.Contains(directoryName, StringComparer.OrdinalIgnoreCase)) diff --git a/src/FileTree.Core/Filtering/FilterContext.cs b/src/FileTree.Core/Filtering/FilterContext.cs index c93a5e8..ca9168c 100644 --- a/src/FileTree.Core/Filtering/FilterContext.cs +++ b/src/FileTree.Core/Filtering/FilterContext.cs @@ -3,14 +3,21 @@ namespace FileTree.Core.Filtering; +/// +/// Context for filter operations: combines with optional gitignore rules. +/// Passed to rule loaders/evaluators. +/// internal class FilterContext { + /// Initializes with options and optional gitignore. public FilterContext(FilterOptions options, GitIgnoreRules? gitIgnoreRules = null) { Options = options; GitIgnoreRules = gitIgnoreRules; } + /// Filter options from user input. public FilterOptions Options { get; } + /// Loaded .gitignore rules if applicable. public GitIgnoreRules? GitIgnoreRules { get; } } \ No newline at end of file diff --git a/src/FileTree.Core/Filtering/FilterRulesLoader.cs b/src/FileTree.Core/Filtering/FilterRulesLoader.cs index 2a91735..c85f52c 100644 --- a/src/FileTree.Core/Filtering/FilterRulesLoader.cs +++ b/src/FileTree.Core/Filtering/FilterRulesLoader.cs @@ -10,6 +10,11 @@ namespace FileTree.Core.Filtering; /// Loads filtering rules from multiple sources and combines them into a single ordered list. /// Rules are applied in order of precedence: App global -> Default global -> Custom global -> Local config -> Inline rules. /// +/// +/// Loads filter rules from various sources (app/global/local configs, inline) in precedence order. +/// Parses .filetreeignore files, combines into ordered list for gitignore matching. +/// Cross-platform home dir detection. +/// internal class FilterRulesLoader { private const string DefaultGlobalConfigFileName = ".filetreeignore"; @@ -19,6 +24,7 @@ internal class FilterRulesLoader /// /// Configuration specifying which rule sources to load. /// A list of combined rules in the correct order. + /// Loads rules from all sources in order, adding to list. public List LoadFilterRules(FilterRulesSource source) { if (source == null) diff --git a/src/FileTree.Core/Filtering/IFileFilter.cs b/src/FileTree.Core/Filtering/IFileFilter.cs index 447e43c..6401ecc 100644 --- a/src/FileTree.Core/Filtering/IFileFilter.cs +++ b/src/FileTree.Core/Filtering/IFileFilter.cs @@ -1,7 +1,14 @@ namespace FileTree.Core.Filtering; +/// +/// Legacy filtering interface for file/dir inclusion decisions. +/// Used for backward compatibility with list-based filters. +/// New code should use gitignore-style FilterRulesSource. +/// internal interface IFileFilter { + /// Decides if file should be included based on name/path lists. bool ShouldIncludeFile(string fileName, string fullPath); + /// Decides if directory should be included based on name/path lists. bool ShouldIncludeDirectory(string directoryName, string fullPath); } diff --git a/src/FileTree.Core/Filtering/LegacyFilterConverter.cs b/src/FileTree.Core/Filtering/LegacyFilterConverter.cs index 6c05acb..aedf78b 100644 --- a/src/FileTree.Core/Filtering/LegacyFilterConverter.cs +++ b/src/FileTree.Core/Filtering/LegacyFilterConverter.cs @@ -9,6 +9,10 @@ namespace FileTree.Core.Filtering; /// Converts legacy FilterOptions properties to gitignore-style filtering rules. /// This ensures backward compatibility with code using the old filtering API. /// +/// +/// Converts legacy list-based FilterOptions to gitignore-style rules for compatibility. +/// Generates patterns like "**/*.ext" or "!**/*name". +/// internal static class LegacyFilterConverter { /// @@ -26,6 +30,7 @@ internal static class LegacyFilterConverter /// Note: The order matters in gitignore. Include rules must come after exclude rules /// to properly negate them. /// + /// Main conversion: lists to gitignore patterns. public static List ConvertToGitIgnoreRules(FilterOptions options) { if (options == null) diff --git a/src/FileTree.Core/Formatting/AsciiTreeFormatter.cs b/src/FileTree.Core/Formatting/AsciiTreeFormatter.cs index f088661..7a0feb1 100644 --- a/src/FileTree.Core/Formatting/AsciiTreeFormatter.cs +++ b/src/FileTree.Core/Formatting/AsciiTreeFormatter.cs @@ -3,8 +3,13 @@ namespace FileTree.Core.Formatting; +/// +/// ASCII art tree formatter using | / \ - characters. +/// Compatible with all terminals. +/// internal class AsciiTreeFormatter : ITreeFormatter { + /// Formats tree using ASCII characters. Root printed first, then children with connectors. public string Format(FileNode root, FormatContext context) { var sb = new StringBuilder(); @@ -21,6 +26,7 @@ public string Format(FileNode root, FormatContext context) return sb.ToString(); } + /// Formats single node name, handling collapsed placeholders and hidden styling. private static string FormatNode(FileNode node, FormatContext context) { if (node.IsCollapsedPlaceholder) diff --git a/src/FileTree.Core/Formatting/CollapsePlaceholderFormatter.cs b/src/FileTree.Core/Formatting/CollapsePlaceholderFormatter.cs index 127d526..320db5a 100644 --- a/src/FileTree.Core/Formatting/CollapsePlaceholderFormatter.cs +++ b/src/FileTree.Core/Formatting/CollapsePlaceholderFormatter.cs @@ -2,14 +2,26 @@ namespace FileTree.Core.Formatting; +/// +/// Formats placeholder strings for collapsed nodes based on : Simple "...", Count, ByExtension. +/// Called by formatters for IsCollapsedPlaceholder nodes. +/// internal static class CollapsePlaceholderFormatter { + /// Main entry: Builds text, adds Markdown backticks if needed. + /// Collapsed node with counts. + /// Context for format-specific wrapping. + /// Placeholder string. internal static string Format(FileNode node, FormatContext context) { var text = BuildText(node, context.Options); return context.Format == OutputFormat.Markdown ? $"`{text}`" : text; } + /// Builds core placeholder text based on style/counts/extension hint. + /// Node stats. + /// Collapse style/options. + /// Text like "... (3 files)" or "... (12 .cs)". internal static string BuildText(FileNode node, FileTreeOptions options) { if (options.CollapseStyle == CollapseStyle.Simple) diff --git a/src/FileTree.Core/Formatting/FormatContext.cs b/src/FileTree.Core/Formatting/FormatContext.cs index 4cb2fad..9c13ac3 100644 --- a/src/FileTree.Core/Formatting/FormatContext.cs +++ b/src/FileTree.Core/Formatting/FormatContext.cs @@ -2,13 +2,21 @@ namespace FileTree.Core.Formatting; +/// +/// Immutable context passed to formatters, providing access to . +/// Enables format-specific options without passing options directly. +/// internal sealed class FormatContext { + /// Creates context from options. + /// Formatting options. public FormatContext(FileTreeOptions options) { Options = options ?? throw new ArgumentNullException(nameof(options)); } + /// Original options. public FileTreeOptions Options { get; } + /// Shortcut to . public OutputFormat Format => Options.Format; } diff --git a/src/FileTree.Core/Formatting/ITreeFormatter.cs b/src/FileTree.Core/Formatting/ITreeFormatter.cs index 8672bd8..201c5d3 100644 --- a/src/FileTree.Core/Formatting/ITreeFormatter.cs +++ b/src/FileTree.Core/Formatting/ITreeFormatter.cs @@ -2,7 +2,17 @@ namespace FileTree.Core.Formatting; +/// +/// Contract for formatting a tree into string representation. +/// Supports different output styles (ASCII, Unicode, Markdown). +/// internal interface ITreeFormatter { + /// + /// Formats the tree hierarchy into a string. + /// + /// Root node of the tree. + /// Formatting context (options, styles). + /// Formatted tree string ready for output. string Format(FileNode root, FormatContext context); } diff --git a/src/FileTree.Core/Formatting/MarkdownTreeFormatter.cs b/src/FileTree.Core/Formatting/MarkdownTreeFormatter.cs index 0850e80..16bad32 100644 --- a/src/FileTree.Core/Formatting/MarkdownTreeFormatter.cs +++ b/src/FileTree.Core/Formatting/MarkdownTreeFormatter.cs @@ -3,8 +3,14 @@ namespace FileTree.Core.Formatting; +/// +/// Markdown list-style tree formatter. +/// Indented bullet-less list with / for dirs. +/// Ideal for GitHub README. +/// internal class MarkdownTreeFormatter : ITreeFormatter { + /// Formats tree as indented Markdown list. Recursive write. public string Format(FileNode root, FormatContext context) { var sb = new StringBuilder(); @@ -12,6 +18,11 @@ public string Format(FileNode root, FormatContext context) return sb.ToString(); } + /// Recursively writes node at depth, with 2-space indent. + /// Current node. + /// Builder to append to. + /// Indent level. + /// Formatting context. private void WriteNode(FileNode node, StringBuilder sb, int depth, FormatContext context) { var indent = new string(' ', depth * 2); @@ -22,6 +33,7 @@ private void WriteNode(FileNode node, StringBuilder sb, int depth, FormatContext foreach (var child in node.Children) WriteNode(child, sb, depth + 1, context); } + /// Formats node name: dir / suffix, hidden styling, collapsed placeholders. private static string FormatNode(FileNode node, FormatContext context) { if (node.IsCollapsedPlaceholder) diff --git a/src/FileTree.Core/Formatting/NodeNameStyler.cs b/src/FileTree.Core/Formatting/NodeNameStyler.cs index 433cbfa..ae67c19 100644 --- a/src/FileTree.Core/Formatting/NodeNameStyler.cs +++ b/src/FileTree.Core/Formatting/NodeNameStyler.cs @@ -2,8 +2,17 @@ namespace FileTree.Core.Formatting; +/// +/// Utility for styling node names: applies hidden file markers (prefix/suffix/minimal) based on options. +/// Used by all tree formatters. +/// internal static class NodeNameStyler { + /// Applies hidden styling if enabled and node is hidden. + /// Base name. + /// Node metadata. + /// Context for style. + /// Styled name. internal static string Apply(string name, FileNode node, FormatContext context) { if (string.IsNullOrEmpty(name)) @@ -19,6 +28,7 @@ internal static string Apply(string name, FileNode node, FormatContext context) return ApplyHiddenStyle(name, context); } + /// Applies the configured hidden style to name. private static string ApplyHiddenStyle(string name, FormatContext context) { return context.Options.HiddenStyle switch diff --git a/src/FileTree.Core/Formatting/TreeFormatterFactory.cs b/src/FileTree.Core/Formatting/TreeFormatterFactory.cs index 4a7b36c..09cda47 100644 --- a/src/FileTree.Core/Formatting/TreeFormatterFactory.cs +++ b/src/FileTree.Core/Formatting/TreeFormatterFactory.cs @@ -2,8 +2,16 @@ namespace FileTree.Core.Formatting; +/// +/// Factory for selecting concrete implementations based on . +/// Simple switch, extensible for new formats. +/// internal class TreeFormatterFactory { + /// Creates formatter for the given format. + /// Desired output format. + /// Appropriate formatter instance. + /// Unknown format. public ITreeFormatter Create(OutputFormat format) { return format switch diff --git a/src/FileTree.Core/Formatting/UnicodeTreeFormatter.cs b/src/FileTree.Core/Formatting/UnicodeTreeFormatter.cs index b44265e..095f164 100644 --- a/src/FileTree.Core/Formatting/UnicodeTreeFormatter.cs +++ b/src/FileTree.Core/Formatting/UnicodeTreeFormatter.cs @@ -3,8 +3,13 @@ namespace FileTree.Core.Formatting; +/// +/// Unicode box-drawing tree formatter (│ ├ └ ─). Pretty terminal output. +/// Similar structure to Ascii, different chars. +/// internal class UnicodeTreeFormatter : ITreeFormatter { + /// Formats tree using Unicode box characters. Root first, then children. public string Format(FileNode root, FormatContext context) { var sb = new StringBuilder(); @@ -21,6 +26,7 @@ public string Format(FileNode root, FormatContext context) return sb.ToString(); } + /// Formats node name, delegating to placeholder/styler. private static string FormatNode(FileNode node, FormatContext context) { if (node.IsCollapsedPlaceholder) diff --git a/src/FileTree.Core/GitIgnore/GitIgnoreParser.cs b/src/FileTree.Core/GitIgnore/GitIgnoreParser.cs index 8086fe4..077ecfa 100644 --- a/src/FileTree.Core/GitIgnore/GitIgnoreParser.cs +++ b/src/FileTree.Core/GitIgnore/GitIgnoreParser.cs @@ -4,10 +4,16 @@ namespace FileTree.Core.GitIgnore { + /// + /// Factory methods for from empty, lines, or .gitignore file. + /// Handles parsing, comment stripping, trimming. + /// public static class GitIgnoreParser { + /// Creates empty rules instance. public static GitIgnoreRules Create() => new GitIgnoreRules(); + /// Parses lines, ignores empty/comments, adds to new rules. public static GitIgnoreRules FromLines(IEnumerable lines) { if (lines == null) throw new ArgumentNullException(nameof(lines)); @@ -23,6 +29,7 @@ public static GitIgnoreRules FromLines(IEnumerable lines) return rules; } + /// Loads from file path, reads lines then FromLines. public static GitIgnoreRules FromFile(string gitIgnoreFilePath) { if (string.IsNullOrWhiteSpace(gitIgnoreFilePath)) throw new ArgumentNullException(nameof(gitIgnoreFilePath)); diff --git a/src/FileTree.Core/GitIgnore/GitIgnoreRules.cs b/src/FileTree.Core/GitIgnore/GitIgnoreRules.cs index 8806f3f..4ca1ee7 100644 --- a/src/FileTree.Core/GitIgnore/GitIgnoreRules.cs +++ b/src/FileTree.Core/GitIgnore/GitIgnoreRules.cs @@ -5,15 +5,22 @@ namespace FileTree.Core.GitIgnore { + /// + /// Wrapper around Ignore library for gitignore-style path matching. + /// Fluent Add rules, query IsIgnored/Filter. + /// public class GitIgnoreRules { private readonly Ignore.Ignore _ignore; + /// Initializes empty matcher. public GitIgnoreRules() { _ignore = new Ignore.Ignore(); } + /// Adds single rule, fluent. + /// Pattern. public GitIgnoreRules Add(string rule) { if (rule == null) throw new ArgumentNullException(nameof(rule)); @@ -21,6 +28,7 @@ public GitIgnoreRules Add(string rule) return this; } + /// Adds rules enumerable, cleans empty/comments, fluent. public GitIgnoreRules Add(IEnumerable rules) { if (rules == null) throw new ArgumentNullException(nameof(rules)); @@ -30,12 +38,14 @@ public GitIgnoreRules Add(IEnumerable rules) return this; } + /// Filters paths not ignored. public IEnumerable Filter(IEnumerable paths) { if (paths == null) throw new ArgumentNullException(nameof(paths)); return _ignore.Filter(paths); } + /// Tests if path ignored. public bool IsIgnored(string path) { if (path == null) throw new ArgumentNullException(nameof(path)); diff --git a/src/FileTree.Core/Models/CollapseStyle.cs b/src/FileTree.Core/Models/CollapseStyle.cs index 45375b8..f006bb2 100644 --- a/src/FileTree.Core/Models/CollapseStyle.cs +++ b/src/FileTree.Core/Models/CollapseStyle.cs @@ -2,7 +2,10 @@ namespace FileTree.Core.Models; public enum CollapseStyle { + /// ... placeholder. Simple, + /// ... (5 more) with count. Count, + /// ... [.cs x12] by extension. ByExtension } \ No newline at end of file diff --git a/src/FileTree.Core/Models/FileNode.cs b/src/FileTree.Core/Models/FileNode.cs index 9ebac48..3c576dc 100644 --- a/src/FileTree.Core/Models/FileNode.cs +++ b/src/FileTree.Core/Models/FileNode.cs @@ -2,10 +2,20 @@ namespace FileTree.Core.Models; +/// +/// Represents a node in the file tree. +/// Leaf for files, container for dirs. Supports collapse placeholders. +/// Immutable except children/collapsed props. +/// public class FileNode { private readonly List _children = new(); + /// Constructor. + /// Basename. + /// Absolute path. + /// Dir or file. + /// Hidden flag. public FileNode(string name, string fullPath, bool isDirectory, bool isHidden = false) { Name = name; @@ -14,22 +24,36 @@ public FileNode(string name, string fullPath, bool isDirectory, bool isHidden = IsHidden = isHidden; } + /// Basename (no path). public string Name { get; } + /// Absolute path. public string FullPath { get; } + /// True if directory. public bool IsDirectory { get; } + /// True if hidden. public bool IsHidden { get; } + /// Child nodes (read-only). public IReadOnlyList Children => _children; + /// True if placeholder for collapsed children. public bool IsCollapsedPlaceholder { get; set; } + /// Total hidden children count. public int CollapsedCount { get; set; } + /// Hidden file children count. public int CollapsedFileCount { get; set; } + /// Hidden folder children count. public int CollapsedFolderCount { get; set; } + /// Extension hint for ByExtension collapse. public string? CollapsedExtensionHint { get; set; } + /// Adds child (during scan). + /// Child node. public void AddChild(FileNode child) { _children.Add(child); } + /// Removes child (post-filter). + /// Child to remove. public void RemoveChild(FileNode child) { _children.Remove(child); diff --git a/src/FileTree.Core/Models/FileTreeOptions.cs b/src/FileTree.Core/Models/FileTreeOptions.cs index 3fa0e80..f7923c6 100644 --- a/src/FileTree.Core/Models/FileTreeOptions.cs +++ b/src/FileTree.Core/Models/FileTreeOptions.cs @@ -1,19 +1,38 @@ namespace FileTree.Core.Models; +/// +/// Core configuration for tree generation. +/// Controls scanning limits, filtering, formatting, collapse. +/// Defaults favor reasonable output; -1/-1 unlimited. +/// public class FileTreeOptions { + /// Max recursion depth. -1 unlimited (default). public int MaxDepth { get; init; } = -1; + /// Max siblings per dir. -1 unlimited (default). public int MaxWidth { get; init; } = -1; + /// Global node cap. -1 unlimited (default). public int MaxNodes { get; init; } = -1; + /// Load/apply .gitignore rules from root. Default: false. public bool UseGitIgnore { get; init; } + /// Exclude hidden files/dirs. Default: false. public bool SkipHidden { get; init; } + /// Special style for hidden (if not skipped). Default: true. public bool HighlightHiddenFiles { get; init; } = true; + /// Hidden marker style. Default: Prefix. public HiddenStyle HiddenStyle { get; init; } = HiddenStyle.Prefix; + /// Tree render style (Ascii/Markdown/Unicode). Default: Ascii. public OutputFormat Format { get; init; } = OutputFormat.Ascii; + /// Filtering config (rules, legacy). public FilterOptions Filter { get; init; } = new(); + /// Collapse dirs > N children. Null=auto. public int? CollapseThreshold { get; init; } + /// First N children to show in collapsed. Default: 1. public int CollapseKeepStart { get; init; } = 1; + /// Last N children to show in collapsed. Default: 1. public int CollapseKeepEnd { get; init; } = 1; + /// Placeholder style. Default: Count. public CollapseStyle CollapseStyle { get; init; } = CollapseStyle.Count; + /// Start collapsing from this depth (1=root children). Default: 1. public int CollapseFrom { get; init; } = 1; } diff --git a/src/FileTree.Core/Models/FilterOptions.cs b/src/FileTree.Core/Models/FilterOptions.cs index 199339f..5839c15 100644 --- a/src/FileTree.Core/Models/FilterOptions.cs +++ b/src/FileTree.Core/Models/FilterOptions.cs @@ -2,6 +2,10 @@ namespace FileTree.Core.Models; +/// +/// Filtering configuration. +/// Modern: RulesSource gitignore-style. Legacy: ext/name lists (deprecated). +/// public class FilterOptions { /// diff --git a/src/FileTree.Core/Models/HiddenStyle.cs b/src/FileTree.Core/Models/HiddenStyle.cs index 30ab137..1367b22 100644 --- a/src/FileTree.Core/Models/HiddenStyle.cs +++ b/src/FileTree.Core/Models/HiddenStyle.cs @@ -1,8 +1,15 @@ namespace FileTree.Core.Models; +/// +/// Style for marking hidden nodes. +/// public enum HiddenStyle { + /// [hidden] prefix. Prefix, + /// (hidden) suffix. Suffix, + /// * minimal marker. Minimal } + diff --git a/src/FileTree.Core/Models/OutputFormat.cs b/src/FileTree.Core/Models/OutputFormat.cs index 708ce5d..ec16ce6 100644 --- a/src/FileTree.Core/Models/OutputFormat.cs +++ b/src/FileTree.Core/Models/OutputFormat.cs @@ -1,8 +1,15 @@ namespace FileTree.Core.Models; +/// +/// Tree rendering format. +/// public enum OutputFormat { + /// ASCII box chars (| / \ -). Ascii, + /// Markdown tree list. Markdown, + /// Unicode tree chars (│ ├ └ ─). Unicode + } \ No newline at end of file diff --git a/src/FileTree.Core/Processing/CollapseProcessor.cs b/src/FileTree.Core/Processing/CollapseProcessor.cs index 17389d3..05b6bb8 100644 --- a/src/FileTree.Core/Processing/CollapseProcessor.cs +++ b/src/FileTree.Core/Processing/CollapseProcessor.cs @@ -3,8 +3,14 @@ namespace FileTree.Core.Processing; +/// +/// Default implementing tree collapse logic. +/// Recursively clones tree, replacing large dirs with placeholders. +/// Supports KeepStart/KeepEnd, from depth, different styles. +/// internal sealed class CollapseProcessor : ITreeProcessor { + /// Processes tree if threshold set, using recursive clone/collapse. public FileNode Process(FileNode root, FileTreeOptions options) { if (root == null) throw new ArgumentNullException(nameof(root)); diff --git a/src/FileTree.Core/Processing/ITreeProcessor.cs b/src/FileTree.Core/Processing/ITreeProcessor.cs index 5224351..09905e6 100644 --- a/src/FileTree.Core/Processing/ITreeProcessor.cs +++ b/src/FileTree.Core/Processing/ITreeProcessor.cs @@ -2,7 +2,15 @@ namespace FileTree.Core.Processing; +/// +/// Post-scan tree processor pipeline interface. +/// Transforms tree (e.g., collapse nodes, prune, etc.) based on options. +/// internal interface ITreeProcessor { + /// Processes/transforms the tree root. + /// Input tree. + /// Processing options (threshold etc.). + /// Processed tree (may mutate/replace nodes). FileNode Process(FileNode root, FileTreeOptions options); } \ No newline at end of file diff --git a/src/FileTree.Core/Scanning/FileScanner.cs b/src/FileTree.Core/Scanning/FileScanner.cs index 0d4e3bc..e406254 100644 --- a/src/FileTree.Core/Scanning/FileScanner.cs +++ b/src/FileTree.Core/Scanning/FileScanner.cs @@ -9,10 +9,22 @@ namespace FileTree.Core.Scanning { - internal class FileScanner : IFileScanner +/// +/// Implements tree scanning with limits, filtering, gitignore. +/// Recursive, depth/node aware. +/// +/// +/// Default implementation of . Handles recursive directory scanning with support for +/// depth/width/node limits, .gitignore rules, custom filters (legacy/new), hidden files, empty folder pruning. +/// Uses stack-based Enter/Exit for local .filetreeignore inheritance. +/// +internal class FileScanner : IFileScanner { + /// Total nodes scanned (for MaxNodes limit). private int _nodeCount; + /// Whether to prune empty folders post-scan. private bool _ignoreEmptyFolders; + /// Evaluator for inclusion rules (limits, filters, gitignore). private ScanInclusionEvaluator? _inclusionEvaluator; /// @@ -66,6 +78,14 @@ public FileNode Scan(string rootPath, FileTreeOptions options) return rootNode; } + /// + /// Recursively scans directory contents, applying inclusion rules and limits. + /// Builds child FileNode, recurses dirs, prunes empty if configured. + /// + /// Current directory. + /// Parent node to add children to. + /// Recursion depth. + /// Scan options. private void PerformScan(DirectoryInfo dirInfo, FileNode parentNode, int currentDepth, FileTreeOptions options) { // MaxDepth and MaxNodes checks are now handled by ScanInclusionEvaluator, diff --git a/src/FileTree.Core/Scanning/HiddenFileDetector.cs b/src/FileTree.Core/Scanning/HiddenFileDetector.cs index c316da0..8e9232b 100644 --- a/src/FileTree.Core/Scanning/HiddenFileDetector.cs +++ b/src/FileTree.Core/Scanning/HiddenFileDetector.cs @@ -3,8 +3,25 @@ namespace FileTree.Core.Scanning { + /// + /// Cross-platform detector for hidden files and directories. + /// On Windows, checks the Hidden file attribute. + /// On Unix-like systems, checks for dot-prefix in name (e.g., .git). + /// internal static class HiddenFileDetector { + /// + /// Determines if the specified file system item is hidden. + /// + /// The file or directory information to check. + /// + /// true if the item is hidden; otherwise, false. + /// + /// + /// Platform-specific logic: + /// - Windows: Checks . + /// - Other OS: Checks if name starts with '.'. + /// internal static bool IsHidden(FileSystemInfo item) { if (OperatingSystem.IsWindows()) diff --git a/src/FileTree.Core/Scanning/IFileTreeScanner.cs b/src/FileTree.Core/Scanning/IFileTreeScanner.cs index 53d8245..9814891 100644 --- a/src/FileTree.Core/Scanning/IFileTreeScanner.cs +++ b/src/FileTree.Core/Scanning/IFileTreeScanner.cs @@ -2,7 +2,20 @@ namespace FileTree.Core.Scanning; +/// +/// Defines the scanning contract for building file tree structures from directories. +/// Implementations handle recursion, limits, filtering during scan. +/// Note: Named IFileScanner internally, but file is IFileTreeScanner.cs. +/// internal interface IFileScanner { + /// + /// Scans the directory tree starting from , building a hierarchy. + /// Applies scanning limits, hidden/gitignore checks based on . + /// + /// Absolute path to root directory. + /// Scan configuration (depth, width, filters). + /// Root representing the tree. + /// Access denied during scan. FileNode Scan(string rootPath, FileTreeOptions options); } diff --git a/src/FileTree.Core/Scanning/ScanInclusionEvaluator.cs b/src/FileTree.Core/Scanning/ScanInclusionEvaluator.cs index f69c6ec..24748bb 100644 --- a/src/FileTree.Core/Scanning/ScanInclusionEvaluator.cs +++ b/src/FileTree.Core/Scanning/ScanInclusionEvaluator.cs @@ -9,6 +9,11 @@ namespace FileTree.Core.Scanning { + /// + /// Evaluates whether file system items should be included in the scan based on limits, filters, gitignore rules, hidden status, etc. + /// Supports hierarchical .filetreeignore files and legacy filter conversion. + /// Uses stack for local rules scope. + /// internal class ScanInclusionEvaluator { private readonly GitIgnoreRules? _gitIgnoreRules; @@ -19,6 +24,7 @@ internal class ScanInclusionEvaluator private readonly List> _localRulesStack = new(); private readonly List _baseFilterRules; private readonly bool _useLocalFilterFiles; + /// Name of local ignore file for directory-specific rules. private const string LocalIgnoreFileName = ".filetreeignore"; /// @@ -28,6 +34,8 @@ internal class ScanInclusionEvaluator /// File tree scanning options. /// Optional .gitignore rules to apply. /// Optional custom filter rules to apply. + /// Initializes evaluator with root path, options, and rules sources. + /// Combines gitignore, custom rules, legacy filters. Builds initial filter rules. public ScanInclusionEvaluator( string rootPath, FileTreeOptions options, @@ -110,6 +118,11 @@ public bool ShouldInclude(FileSystemInfo item, int currentDepth, int currentNode return true; } + /// + /// Loads .filetreeignore from dir if exists, pushes to stack, rebuilds rules. + /// + /// Directory to check for local ignore. + /// True if rules loaded/pushed. public bool EnterDirectory(DirectoryInfo dirInfo) { if (!_useLocalFilterFiles) @@ -134,6 +147,8 @@ public bool EnterDirectory(DirectoryInfo dirInfo) return true; } + /// Pops local rules from stack if previously pushed, rebuilds effective rules. + /// True if EnterDirectory loaded rules. public void ExitDirectory(bool hadRules) { if (!hadRules) diff --git a/src/FileTree.Core/Services/FileTreeService.cs b/src/FileTree.Core/Services/FileTreeService.cs index c19e15e..c66a719 100644 --- a/src/FileTree.Core/Services/FileTreeService.cs +++ b/src/FileTree.Core/Services/FileTreeService.cs @@ -8,12 +8,20 @@ namespace FileTree.Core.Services; +/// +/// Main service orchestrating tree generation: scan -> process -> format. +/// DI-friendly ctor for testing. +/// public class FileTreeService : IFileTreeService { private readonly IFileScanner _scanner; private readonly TreeFormatterFactory _formatterFactory; private readonly IReadOnlyList _processors; + /// DI ctor. + /// Tree scanner. + /// Formatter selector. + /// Optional processors (default collapse). internal FileTreeService( IFileScanner scanner, TreeFormatterFactory formatterFactory, @@ -24,11 +32,13 @@ internal FileTreeService( _processors = processors?.ToList() ?? new List { new CollapseProcessor() }; } + /// Default ctor with built-in scanner/factory/collapse. public FileTreeService() : this(new FileScanner(), new TreeFormatterFactory()) { } + /// /// Generates a formatted file tree representation for the specified directory. /// diff --git a/src/FileTree.Core/Utilities/PathValidationException.cs b/src/FileTree.Core/Utilities/PathValidationException.cs index a7f78b0..ee5ff66 100644 --- a/src/FileTree.Core/Utilities/PathValidationException.cs +++ b/src/FileTree.Core/Utilities/PathValidationException.cs @@ -3,6 +3,10 @@ /// /// Custom exception for path validation errors in FileTree scanning. /// +/// +/// Entry namespace for path validation errors. +/// Contains PathErrorType enum and PathValidationException class. +/// namespace FileTree.Core.Utilities { /// diff --git a/tests/FileTree.Core.Tests/Fixtures/TempDirectoryFixture.cs b/tests/FileTree.Core.Tests/Fixtures/TempDirectoryFixture.cs index aeee3f6..005e2cb 100644 --- a/tests/FileTree.Core.Tests/Fixtures/TempDirectoryFixture.cs +++ b/tests/FileTree.Core.Tests/Fixtures/TempDirectoryFixture.cs @@ -3,6 +3,10 @@ namespace FileTree.Core.Tests.Fixtures { + /// + /// IDisposable fixture for creating temporary directories/files for integration tests. + /// Cleans up on Dispose. +/// public class TempDirectoryFixture : IDisposable { public string RootPath { get; } diff --git a/tests/FileTree.Core.Tests/Fixtures/TestTreeFixture.cs b/tests/FileTree.Core.Tests/Fixtures/TestTreeFixture.cs index 8912b1b..301e1e5 100644 --- a/tests/FileTree.Core.Tests/Fixtures/TestTreeFixture.cs +++ b/tests/FileTree.Core.Tests/Fixtures/TestTreeFixture.cs @@ -2,8 +2,14 @@ namespace FileTree.Core.Tests.Fixtures; +/// +/// Fixture providing a standard test tree structure for formatter/scanner tests. +/// Creates in-memory FileNode hierarchy with root/bin/src/docs/temp/config. +/// public class TestTreeFixture { + /// Creates the test tree with known structure for assertions. + /// Root FileNode. public FileNode CreateTestTree() { // root/ diff --git a/tests/FileTree.Core.Tests/Scanning/FileScannerTests.cs b/tests/FileTree.Core.Tests/Scanning/FileScannerTests.cs index cf746d0..dd44ed3 100644 --- a/tests/FileTree.Core.Tests/Scanning/FileScannerTests.cs +++ b/tests/FileTree.Core.Tests/Scanning/FileScannerTests.cs @@ -7,6 +7,10 @@ namespace FileTree.Tests.Scanning { + /// + /// Tests for FileScanner.Scan: basic scan, limits, hidden, gitignore. + /// Uses TempDirectoryFixture implicitly via manual temp dir. + /// public class FileScannerTests : IDisposable { private readonly string _tempRoot; @@ -28,6 +32,7 @@ public void Dispose() } } + /// Default options builder with overrides for test variations. private FileTreeOptions Options(FileTreeOptions overrides = null) { var baseOptions = new FileTreeOptions