Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 136 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

50 changes: 50 additions & 0 deletions src/FileTree.CLI/AppPaths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

namespace FileTree.CLI;

/// <summary>
/// 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.
/// </summary>
internal static class AppPaths
{
/// <summary>Filename for global gitignore-style filter rules near executable.</summary>
internal const string GlobalIgnoreFileName = "FileTree.ignore.txt";
/// <summary>Filename for default CLI options/settings near executable.</summary>
internal const string GlobalSettingsFileName = "FileTree.settings.txt";
private const string DefaultGlobalIgnoreContents =
"# FileTree global ignore rules\n" +
Expand Down Expand Up @@ -35,6 +42,11 @@ internal static string GetExecutablePath()
return modulePath;
}

/// <summary>
/// Gets directory containing the FileTree executable.
/// Prefers AppContext.BaseDirectory, falls back to parent of exe path.
/// </summary>
/// <returns>Normalized full directory path.</returns>
internal static string GetExecutableDirectory()
{
if (!string.IsNullOrWhiteSpace(AppContext.BaseDirectory))
Expand All @@ -45,16 +57,26 @@ internal static string GetExecutableDirectory()
return Path.GetDirectoryName(GetExecutablePath())!;
}

/// <summary>Gets path to global FileTree.ignore.txt next to executable.</summary>
/// <returns>Full path (dir may not exist).</returns>
internal static string GetGlobalIgnorePath()
{
return Path.Combine(GetExecutableDirectory(), GlobalIgnoreFileName);
}

/// <summary>Gets path to global FileTree.settings.txt next to executable.</summary>
/// <returns>Full path (dir may not exist).</returns>
internal static string GetGlobalSettingsPath()
{
return Path.Combine(GetExecutableDirectory(), GlobalSettingsFileName);
}

/// <summary>
/// Ensures FileTree.ignore.txt exists (creates with defaults if missing).
/// </summary>
/// <param name="path">Output: full file path.</param>
/// <param name="error">Output: null on success, else exception message.</param>
/// <returns>true if exists or created, false on error.</returns>
internal static bool TryEnsureGlobalIgnoreFileExists(out string path, out string? error)
{
path = GetGlobalIgnorePath();
Expand All @@ -77,6 +99,10 @@ internal static bool TryEnsureGlobalIgnoreFileExists(out string path, out string
}
}

/// <summary>Overwrites FileTree.ignore.txt with default content.</summary>
/// <param name="path">Output: full file path.</param>
/// <param name="error">Output: null on success, else exception message.</param>
/// <returns>true if written, false on error (file/dir perms).</returns>
internal static bool TryResetGlobalIgnoreFile(out string path, out string? error)
{
path = GetGlobalIgnorePath();
Expand All @@ -94,6 +120,10 @@ internal static bool TryResetGlobalIgnoreFile(out string path, out string? error
}
}

/// <summary>Ensures FileTree.settings.txt exists (creates with defaults if missing).</summary>
/// <param name="path">Output: full file path.</param>
/// <param name="error">Output: null on success, else exception message.</param>
/// <returns>true if exists or created, false on error.</returns>
internal static bool TryEnsureGlobalSettingsFileExists(out string path, out string? error)
{
path = GetGlobalSettingsPath();
Expand All @@ -116,6 +146,10 @@ internal static bool TryEnsureGlobalSettingsFileExists(out string path, out stri
}
}

/// <summary>Overwrites FileTree.settings.txt with default content.</summary>
/// <param name="path">Output: full file path.</param>
/// <param name="error">Output: null on success, else exception message.</param>
/// <returns>true if written, false on error (perms).</returns>
internal static bool TryResetGlobalSettingsFile(out string path, out string? error)
{
path = GetGlobalSettingsPath();
Expand All @@ -133,6 +167,10 @@ internal static bool TryResetGlobalSettingsFile(out string path, out string? err
}
}

/// <summary>Deletes FileTree.ignore.txt if exists.</summary>
/// <param name="path">Output: attempted file path.</param>
/// <param name="error">Output: null on success, else exception.</param>
/// <returns>true if absent or deleted, false on error.</returns>
internal static bool TryDeleteGlobalIgnoreFile(out string path, out string? error)
{
path = GetGlobalIgnorePath();
Expand All @@ -154,6 +192,10 @@ internal static bool TryDeleteGlobalIgnoreFile(out string path, out string? erro
}
}

/// <summary>Deletes FileTree.settings.txt if exists.</summary>
/// <param name="path">Output: attempted file path.</param>
/// <param name="error">Output: null on success, else exception.</param>
/// <returns>true if absent or deleted, false on error.</returns>
internal static bool TryDeleteGlobalSettingsFile(out string path, out string? error)
{
path = GetGlobalSettingsPath();
Expand All @@ -175,6 +217,10 @@ internal static bool TryDeleteGlobalSettingsFile(out string path, out string? er
}
}

/// <summary>Opens FileTree.settings.txt in default editor (creates if missing).</summary>
/// <param name="path">Output: full file path.</param>
/// <param name="error">Output: null on success/open, else exception.</param>
/// <returns>true if opened (or created+opened), false on error.</returns>
internal static bool TryOpenGlobalSettingsFile(out string path, out string? error)
{
path = GetGlobalSettingsPath();
Expand Down Expand Up @@ -203,6 +249,10 @@ internal static bool TryOpenGlobalSettingsFile(out string path, out string? erro
}
}

/// <summary>Opens FileTree.ignore.txt in default editor (creates if missing).</summary>
/// <param name="path">Output: full file path.</param>
/// <param name="error">Output: null on success/open, else exception.</param>
/// <returns>true if opened (or created+opened), false on error.</returns>
internal static bool TryOpenGlobalIgnoreFile(out string path, out string? error)
{
path = GetGlobalIgnorePath();
Expand Down
Loading