From 0230bdb4365b6e771f54fd3b01df6134ca5fa2e5 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:37:25 +0200 Subject: [PATCH 1/5] Update README.md: Rewrite and expand documentation for `CreativeCoders.Core` library - Reworked structure and added detailed feature explanations. - Included code examples for key functionalities, such as parameter validation, collections, thread-safety, caching, string utilities, reflection, and more. - Improved usage instructions, feature descriptions, and added installation steps for better onboarding. --- source/Core/README.md | 808 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 731 insertions(+), 77 deletions(-) diff --git a/source/Core/README.md b/source/Core/README.md index 3d96f671..58827941 100644 --- a/source/Core/README.md +++ b/source/Core/README.md @@ -1,119 +1,773 @@ -# Core .NET library +# CreativeCoders.Core -Basic classes and interfaces for .NET applications. +[![NuGet](https://img.shields.io/nuget/v/CreativeCoders.Core?style=flat-square)](https://www.nuget.org/packages/CreativeCoders.Core) -## Arguments ensuring -Ensure arguments are as expected +A foundational .NET library providing essential utilities, extensions, and abstractions for building robust C# applications. From parameter validation and thread-safe collections to messaging, caching, and reflection helpers — everything you need to reduce boilerplate and write cleaner code. + +## Installation + +```bash +dotnet add package CreativeCoders.Core +``` + +## Features + +- **Parameter Validation** — Guard clauses with `Ensure` and fluent `Argument` validation +- **Collection Extensions** — LINQ-style helpers like `ForEach`, `Distinct` by key, `WhereNotNull`, and more +- **Thread-Safe Collections** — `ConcurrentList`, `SynchronizedValue`, and configurable locking +- **Observable Collections** — `ExtendedObservableCollection` with batch updates and UI synchronization +- **In-Memory Caching** — Dictionary-based cache with expiration policies and region support +- **Chain of Responsibility** — `HandlerChain` for building processing pipelines +- **Pub/Sub Messaging** — Loosely-coupled messenger with weak reference support +- **Reflection Utilities** — Type discovery, generic instance creation, expression helpers +- **String Utilities** — Case conversion, pattern matching, SecureString support, placeholder replacement +- **Fluent Interface Helpers** — Extension methods for building fluent APIs +- **Environment Abstraction** — Testable `Env` wrapper over `System.Environment` +- **Visitor Pattern** — Full visitor infrastructure with sub-item traversal + +## Usage + +### Ensure — Parameter Validation + +The `Ensure` class provides guard clauses for method parameters with aggressive inlining for zero-overhead validation. -### Simple argument checks for null, empty, etc. -[Ensure.cs](CreativeCoders.Core/Ensure.cs) ```csharp -// Argument name can be given via second parameter or via nameof. If none is given, the name of variable is used. -Ensure.NotNull(instance); // throws ArgumentNullException if instance is null -// Ensure.IsNullOrEmpty works for strings and IEnumerable -Ensure.IsNullOrEmpty(str); // throws ArgumentException if str is null or empty -Ensure.IsNullOrEmpty(items); // throws ArgumentException if items is null or empty -Ensure.FileExists(fileName); // throws ArgumentException if fileName not exists -Ensure.DirectoryExists(dirName); // throws ArgumentException if dirName not exists -// for more checks see Ensure.cs +using CreativeCoders.Core; + +public class UserService(IUserRepository repository) +{ + // Guard constructor-injected dependencies + private readonly IUserRepository _repository = Ensure.NotNull(repository); + + public User GetUser(string email, Guid tenantId) + { + // Guard method arguments + Ensure.IsNotNullOrWhitespace(email); + Ensure.GuidIsNotEmpty(tenantId); + + return _repository.FindByEmail(email, tenantId); + } + + public void UpdateUsers(IEnumerable users) + { + // Guard collections against null or empty + Ensure.IsNotNullOrEmpty(users); + + // Assert arbitrary conditions + Ensure.That(users.All(u => u.IsValid), "All users must be valid"); + + // Validate index ranges + Ensure.IndexIsInRange(0, users.Count()); + } + + public void LoadConfig(string configPath) + { + // Guard file system paths + Ensure.FileExists(configPath); + Ensure.DirectoryExists(Path.GetDirectoryName(configPath)!); + } +} ``` -### More complex argument checks for null, empty, etc. -[Ensure.Argument Extensions](CreativeCoders.Core/EnsureArguments/Extensions) +### Fluent Argument Validation + +For a more fluent approach to parameter validation, use `Ensure.Argument`: + ```csharp -// Argument name can be given via second parameter or via nameof. If none is given, the name of variable is used. -// you can chain the checks to ensure multiple conditions at once -Ensure.Argument(instance).NotNull(); // throws ArgumentNullException if instance is null -Ensure.Argument(text).IsNullOrEmpty(); // throws ArgumentException if str is null or empty -Ensure.Argument(items).IsNullOrEmpty(); // throws ArgumentException if items is null or empty -Ensure.Argument(fileName).FileExists(); // throws ArgumentException if fileName not exists -Ensure.Argument(dirName).DirectoryExists(); // throws ArgumentException if dirName not exists -Ensure.Argument(text).NotNull().HasMaxLength(maxLength); // throws if text is null or exceeds max length +using CreativeCoders.Core; + +public void ProcessOrder(string orderId, int quantity, string text, IEnumerable items) +{ + // Chain multiple checks for one argument + Ensure.Argument(orderId).IsNotNullOrWhitespace(); + Ensure.Argument(items).IsNotNullOrEmpty(); + Ensure.Argument(text).NotNull().HasMaxLength(100); +} ``` -## Threading -Thread-safe collections, synchronization primitives +### Object Extensions -## Enums -Enum extensions and helpers +Utility extensions for safe casting, null-safe operations, and reflection-based property access. ```csharp -enum MyEnum +using CreativeCoders.Core; + +object value = GetValue(); + +// Null-safe ToString +string text = value.ToStringSafe("default"); + +// Safe type casting with default fallback +int number = value.As(42); + +// Try-pattern casting +if (value.TryAs(out var str)) { - [EnumStringValue("Value1")] - ValueOne, - [EnumStringValue("Value2")] - ValueTwo + Console.WriteLine(str); } -// Instantiate a new EnumStringConverter -var enumConverter = new EnumStringConverter(); +// Convert public properties to a dictionary +var user = new { Name = "Alice", Age = 30 }; +Dictionary dict = user.ToDictionary(); -// Convert enum to string -string enumString = enumConverter.Convert(MyEnum.ValueOne); -Console.WriteLine(enumString); // Outputs: Value1 +// Reflection-based property access +var name = someObject.GetPropertyValue("Name"); +someObject.SetPropertyValue("Name", "Bob"); -// Convert string to enum -MyEnum enumValue = enumConverter.Convert("Value2"); -Console.WriteLine(enumValue); // Outputs: ValueTwo +// Polymorphic async disposal +await someObject.TryDisposeAsync(); ``` -## Visitor pattern -Visitor pattern implementation +### DelegateDisposable + +Create `IDisposable` and `IAsyncDisposable` instances from delegates — useful for cleanup callbacks and scope guards. + +```csharp +using CreativeCoders.Core; -## IO -Static helpers for IO operations based on System.IO.Abstractions as a replacement for File, Directory, Path, etc. +// Create a disposable from an action +var cleanup = new DelegateDisposable(() => Console.WriteLine("Cleaned up!"), onlyDisposeOnce: true); -## Weak action and functions -Weak delegates for actions and functions +using (cleanup) +{ + // Do work... +} // "Cleaned up!" is printed -## Reflection -Classes and extensions for working with dynamic code +// Async variant +var asyncCleanup = new DelegateAsyncDisposable( + async () => await SaveStateAsync(), + onlyDisposeOnce: true); -## ObjectLinking -Classes for linking objects together, so that properties of one object are automatically updated when properties of another object change +await using (asyncCleanup) +{ + // Do work... +} +``` -## Dependency tree builder -Classes for building and resolving dependency trees +### Fluent Interface Extensions -## SysEnvironment -Abstraction for Environment class to enable mocking for unit tests +Build fluent APIs by chaining operations that return `this`. -#### Use static methods from Env ```csharp -string desktopPath = Env.GetFolderPath(Environment.SpecialFolder.Desktop); +using CreativeCoders.Core; + +var builder = new StringBuilder() + .Fluent(sb => sb.Append("Hello")) + .Fluent(sb => sb.Append(" World")) + .FluentIf(includeExclamation, sb => sb.Append("!")); + +// Works on any type — great for builder patterns +var config = new ConfigBuilder() + .Fluent(c => c.SetTimeout(30)) + .FluentIf(isDevelopment, c => c.EnableLogging()); ``` -#### Use IEnvironment via DI +### ObservableObject — MVVM Base Class + +A base class implementing `INotifyPropertyChanged` and `INotifyPropertyChanging` for MVVM scenarios. + ```csharp -// First register service -services.AddEnvironment(); +using CreativeCoders.Core; -// Sample app -public class MyApp +public class PersonViewModel : ObservableObject { - private readonly IEnvironment _environment; + private string _name; + private int _age; - public MyApp(IEnvironment environment) + public string Name { - _environment = environment; + get => _name; + set => Set(ref _name, value); } + public int Age + { + get => _age; + set => Set(ref _age, value); + } +} +``` + +### Collections — Enumerable Extensions + +Powerful LINQ-style extensions for everyday collection operations. + +```csharp +using CreativeCoders.Core.Collections; + +var items = new List { "alpha", "beta", "gamma", "delta" }; + +// ForEach with index +items.ForEach((item, index) => Console.WriteLine($"{index}: {item}")); + +// Async iteration +await items.ForEachAsync(async item => await ProcessAsync(item)); + +// Side-effect piping (lazy — executes during enumeration) +var processed = items + .Pipe(item => Log(item)) + .Where(item => item.Length > 4) + .ToList(); + +// Take elements until a condition is met (inclusive) +var untilGamma = items.TakeUntil(x => x == "gamma"); // ["alpha", "beta", "gamma"] + +// Take every Nth element +var everyOther = items.TakeEvery(2); // ["alpha", "gamma"] + +// Filter nulls +var nonNull = mixedList.WhereNotNull(); + +// Check if exactly one element matches +bool single = items.IsSingle(x => x.StartsWith("a")); // true +``` + +**Distinct and duplicate detection by key:** + +```csharp +var people = new List { /* ... */ }; + +// Distinct by single key +var uniqueByName = people.Distinct(p => p.Name); + +// Distinct by multiple keys +var uniqueByNameAndAge = people.Distinct(p => p.Name, p => p.Age); + +// Find duplicates +var duplicates = people.NotDistinct(p => p.Email); +``` + +**Multi-key sorting with direction control:** + +```csharp +using CreativeCoders.Core.Comparing; + +var sorted = people.Sort( + new SortFieldInfo(p => p.LastName, SortOrder.Ascending), + new SortFieldInfo(p => p.Age, SortOrder.Descending)); +``` + +**Choose — filter and transform in one step:** + +```csharp +// Choose combines Where + Select +var parsed = strings.Choose(s => + int.TryParse(s, out var n) ? (true, n) : (false, 0)); +``` + +### Collections — List and Dictionary Extensions + +```csharp +using CreativeCoders.Core.Collections; + +// Add multiple items to any IList +IList list = new List(); +list.AddRange(new[] { "a", "b", "c" }); + +// Replace all items at once +list.SetItems(new[] { "x", "y", "z" }); + +// Reverse dictionary lookup +var dict = new Dictionary { ["alice"] = 1, ["bob"] = 2 }; +string key = dict.GetKeyByValue(2); // "bob" +``` + +### Collections — ExtendedObservableCollection + +Thread-safe observable collection with batch update support and UI synchronization. + +```csharp +using CreativeCoders.Core.Collections; + +var collection = new ExtendedObservableCollection(); + +// Batch updates — raises a single CollectionChanged event +using (collection.Update()) +{ + collection.Add("item1"); + collection.Add("item2"); + collection.Add("item3"); +} + +// Add multiple items at once +collection.AddRange(new[] { "item4", "item5" }); + +// Move items with proper notifications +collection.Move(oldIndex: 0, newIndex: 2); +``` + +### Caching + +In-memory dictionary-based cache with expiration policies and named regions. + +```csharp +using CreativeCoders.Core.Caching; +using CreativeCoders.Core.Caching.Default; + +// Create a cache +var cache = CacheManager.CreateCache(); + +// Get or add with lazy factory +var profile = cache.GetOrAdd("user:123", () => LoadProfileFromDb("123")); + +// Use named regions for logical separation +cache.AddOrUpdate("user:456", profile, regionName: "premium-users"); + +// Try-get pattern +if (cache.TryGet("user:123", out var cached)) +{ + Console.WriteLine(cached.Name); +} + +// Async operations +var asyncProfile = await cache.GetOrAddAsync("user:789", + () => LoadProfileFromDb("789")); + +// Clear a specific region +cache.Clear(regionName: "premium-users"); + +// Remove a single entry +cache.Remove("user:123"); +``` + +### Chaining — Chain of Responsibility + +Build processing pipelines where handlers can choose to handle or pass data along. + +```csharp +using CreativeCoders.Core.Chaining; + +// Define handlers using the delegate-based handler +var handlers = new IChainDataHandler[] +{ + new ChainDataHandler(input => + input.StartsWith("http") + ? new HandleResult(true, $"URL: {input}") + : new HandleResult(false, default!)), + + new ChainDataHandler(input => + input.Contains("@") + ? new HandleResult(true, $"Email: {input}") + : new HandleResult(false, default!)), + + new ChainDataHandler(input => + new HandleResult(true, $"Text: {input}")) +}; + +var chain = new HandlerChain(handlers); + +var result1 = chain.Handle("http://example.com"); // "URL: http://example.com" +var result2 = chain.Handle("user@mail.com"); // "Email: user@mail.com" +var result3 = chain.Handle("hello world"); // "Text: hello world" +``` + +### Comparing — Custom Comparers + +Function-based comparers for use with LINQ, sorted collections, and deduplication. + +```csharp +using CreativeCoders.Core.Comparing; + +// Equality by key selector +var comparer = new FuncEqualityComparer(p => p.Email); +var unique = people.Distinct(comparer); + +// Sorting by key with direction +var sorter = new FuncComparer(p => p.LastName, SortOrder.Ascending); +var sorted = people.Order(sorter); + +// Combine multiple comparers — all must agree for equality +var strict = new MultiEqualityComparer( + new FuncEqualityComparer(p => p.Name), + new FuncEqualityComparer(p => p.Age)); +``` + +### Enums — String Conversion and Flag Enumeration + +```csharp +using CreativeCoders.Core.Enums; + +public enum Status +{ + [EnumStringValue("not-started")] + NotStarted, + + [EnumStringValue("in-progress")] + InProgress, + + [EnumStringValue("done")] + Done +} + +// Convert enum to its string representation +string text = Status.InProgress.ToText(); // "in-progress" + +// Enumerate individual flags from a flags enum +[Flags] +public enum Permissions { Read = 1, Write = 2, Execute = 4 } + +var flags = (Permissions.Read | Permissions.Write); +var individual = flags.EnumerateFlags(); // [Read, Write] +``` + +### Messaging — Pub/Sub Messenger + +A lightweight messenger for loosely-coupled communication between components, with weak reference support to prevent memory leaks. + +```csharp +using CreativeCoders.Core.Messaging; + +// Use the default singleton messenger +var messenger = Messenger.Default; + +// Or create an isolated instance +var isolated = Messenger.CreateInstance(); + +// Register a message handler (returns IDisposable for unregistration) +var registration = messenger.Register(this, message => +{ + Console.WriteLine($"Order {message.OrderId} placed!"); +}); + +// Send a message to all registered handlers +messenger.Send(new OrderPlacedMessage { OrderId = "ORD-001" }); + +// Unregister via IDisposable +registration.Dispose(); + +// Or unregister all handlers for a receiver +messenger.Unregister(this); +``` + +### Placeholders — Template String Replacement + +Replace named placeholders in text with configured values. + +```csharp +using CreativeCoders.Core.Placeholders; + +var placeholders = new Dictionary +{ + ["Name"] = "Alice", + ["Role"] = "Admin", + ["Date"] = DateTime.Now +}; + +var replacer = new PlaceholderReplacer("{", "}", placeholders); + +string result = replacer.Replace("Hello {Name}, you are a {Role} since {Date}."); +// "Hello Alice, you are a Admin since 4/1/2026 ..." + +// Replace in multiple lines at once +var lines = new[] { "User: {Name}", "Role: {Role}" }; +var replaced = replacer.Replace(lines); +``` + +### Reflection — Type Discovery and Utilities + +```csharp +using CreativeCoders.Core.Reflection; + +// Find all implementations of an interface across loaded assemblies +IEnumerable handlers = typeof(ICommandHandler).GetImplementations(); + +// Create a generic type instance dynamically +object? cache = typeof(Cache<>).CreateGenericInstance(typeof(string)); + +// Get default value for any type +object? defaultVal = typeof(int).GetDefault(); // 0 + +// Check if a type implements a generic interface +bool isEnumerable = typeof(List).ImplementsGenericInterface(typeof(IEnumerable<>)); + +// Get generic interface type arguments +Type[] args = typeof(List).GetGenericInterfaceArguments(typeof(IEnumerable<>)); // [int] + +// Extract member names from expressions (useful for MVVM/reflection) +string name = ExpressionExtensions.GetMemberName(p => p.Name); // "Name" +``` + +### Text — String Extensions + +Case conversion, pattern matching, filtering, and more. + +```csharp +using CreativeCoders.Core.Text; + +// Null-safe string checks +string? text = GetText(); +if (text.IsNotNullOrWhiteSpace()) +{ + Console.WriteLine(text); +} + +// Case conversions +"myProperty".CamelCaseToPascalCase(); // "MyProperty" +"my-component".KebabCaseToPascalCase(); // "MyComponent" +"my_field".SnakeCaseToPascalCase(); // "MyField" + +// Character filtering +"Hello, World!".Filter(char.IsLetter); // "HelloWorld" +"user@email.com".Filter('@', '.'); // "useremailcom" + +// Split into key-value pair +var kv = "Content-Type=application/json".SplitIntoKeyValue("="); +// kv.Key == "Content-Type", kv.Value == "application/json" + +// Conditional StringBuilder extensions +var sb = new StringBuilder(); +sb.AppendLineIf(includeHeader, "=== Report ==="); +sb.AppendIf(showTimestamp, $"[{DateTime.Now}] "); +``` + +**Pattern matching with wildcards:** + +```csharp +using CreativeCoders.Core.Text; + +bool match1 = PatternMatcher.MatchesPattern("report.pdf", "*.pdf"); // true +bool match2 = PatternMatcher.MatchesPattern("file01.txt", "file??.txt"); // true +bool match3 = PatternMatcher.MatchesPattern("data.csv", "*.json"); // false +``` + +**Random string generation:** + +```csharp +using CreativeCoders.Core.Text; + +string token = RandomString.Create(); // 128-byte random Base64 string +string short_ = RandomString.Create(32); // 32-byte random Base64 string +``` + +### Text — JSON Serialization Abstraction + +```csharp +using CreativeCoders.Core.Text.Json; + +IJsonSerializer serializer = new DefaultJsonSerializer(); + +// Serialize +string json = serializer.Serialize(new { Name = "Alice", Age = 30 }); + +// Deserialize +var person = serializer.Deserialize(json); + +// Populate an existing object (merge properties) +var existing = new Person { Name = "Bob" }; +serializer.Populate("{\"Age\": 25}", existing); + +// Async stream operations +await using var stream = File.OpenRead("data.json"); +var data = await serializer.DeserializeAsync(stream); +``` + +### Threading — SynchronizedValue + +Thread-safe wrapper for values with configurable locking strategies. + +```csharp +using CreativeCoders.Core.Threading; + +// Simple synchronized value +var counter = SynchronizedValue.Create(0); + +// Thread-safe read and write +counter.Value = 42; +int current = counter.Value; + +// Atomic update +counter.SetValue(c => c + 1); + +// With custom locking mechanism +var lockMechanism = new LockSlimLockingMechanism(); +var sharedState = SynchronizedValue.Create(lockMechanism, "initial"); +``` + +### Threading — ConcurrentList + +A thread-safe `IList` implementation with configurable locking. + +```csharp +using CreativeCoders.Core.Threading; + +var list = new ConcurrentList(); + +// All operations are thread-safe +list.Add("item1"); +list.Add("item2"); +bool contains = list.Contains("item1"); +int count = list.Count; + +// Initialize from existing collection +var fromItems = new ConcurrentList(new[] { 1, 2, 3 }); + +// Use custom locking (e.g., no-lock for single-threaded scenarios) +var noLockList = new ConcurrentList(new NoLockingMechanism()); +``` + +### Threading — Locking Mechanisms + +Pluggable locking strategies for thread synchronization. + +```csharp +using CreativeCoders.Core.Threading; + +// ReaderWriterLockSlim-based (default, best for read-heavy workloads) +ILockingMechanism rwLock = new LockSlimLockingMechanism(); + +rwLock.Read(() => +{ + // Read-locked section — multiple readers allowed + return cache.Get("key"); +}); + +rwLock.Write(() => +{ + // Write-locked section — exclusive access + cache.Set("key", "value"); +}); + +// Simple lock(object)-based +ILockingMechanism simpleLock = new LockLockingMechanism(); + +// No synchronization (for single-threaded code or testing) +ILockingMechanism noLock = new NoLockingMechanism(); +``` + +### IO — File System Utilities + +Extensions for `System.IO.Abstractions` with path safety checks and directory helpers. + +```csharp +using CreativeCoders.Core.IO; + +// Ensure directories exist +FileSys.Directory.EnsureDirectoryExists("/data/exports"); +FileSys.Directory.EnsureDirectoryForFileNameExists("/data/exports/report.csv"); + +// Sanitize file names +string safe = FileSys.Path.ReplaceInvalidFileNameChars("file:name?.txt", "_"); +// "file_name_.txt" + +// Path safety — prevent directory traversal attacks +bool isSafe = FileSys.Path.IsSafe("../../../etc/passwd", "/data/uploads"); // false +FileSys.Path.EnsureSafe(userProvidedPath, allowedBasePath); // throws if unsafe +``` + +### SysEnvironment — Testable Environment Abstraction + +A drop-in replacement for `System.Environment` that can be swapped for testing. + +```csharp +using CreativeCoders.Core.SysEnvironment; + +// Use like System.Environment +string user = Env.UserName; +string machine = Env.MachineName; +string? home = Env.GetEnvironmentVariable("HOME"); + +// Inject a mock for testing +using (Env.SetEnvironmentImpl(mockEnvironment)) +{ + // All Env calls now go through mockEnvironment + var testUser = Env.UserName; // returns mock value +} // Original environment is automatically restored + +// Or use IEnvironment via DI +services.AddEnvironment(); + +public class MyApp(IEnvironment environment) +{ public void Run() { - // Use the IEnvironment instance - Console.WriteLine("Current Directory: " + _environment.CurrentDirectory); - Console.WriteLine("Machine Name: " + _environment.MachineName); - Console.WriteLine("User Name: " + _environment.UserName); - - Console.WriteLine("Environment Variables:"); - foreach (var envVar in _environment.GetEnvironmentVariables()) - { - Console.WriteLine($"Key: {envVar.Key}, Value: {envVar.Value}"); - } + Console.WriteLine($"User: {environment.UserName}"); + Console.WriteLine($"Machine: {environment.MachineName}"); } } ``` -## Null objects -Null objects for various types +### Weak — Weak Reference Delegates + +Hold references to delegates without preventing garbage collection of the owner — essential for event-based architectures. + +```csharp +using CreativeCoders.Core.Weak; + +// Create a weak action — owner can still be GC'd +var action = new WeakAction(myHandler.OnEvent); + +if (action.IsAlive()) +{ + action.Execute(); +} + +// Weak function with return value +var func = new WeakFunc(() => "result"); +string result = func.Execute(); + +// Control owner lifetime explicitly +var keepAlive = new WeakAction(handler.OnEvent, KeepOwnerAliveMode.KeepAlive); +var autoGuess = new WeakAction(handler.OnEvent, KeepOwnerAliveMode.AutoGuess); +``` + +### Error — Error Handler Abstraction + +```csharp +using CreativeCoders.Core.Error; + +// Delegate-based error handler +IErrorHandler handler = new DelegateErrorHandler(ex => + Console.WriteLine($"Error: {ex.Message}")); + +handler.HandleException(new InvalidOperationException("Something went wrong")); + +// Null error handler (no-op, useful as default) +IErrorHandler nullHandler = new NullErrorHandler(); +``` + +### Dependencies — Dependency Resolution + +Resolve and sort dependencies within a graph, with circular reference detection. + +```csharp +using CreativeCoders.Core.Dependencies; + +// Build dependency graph +var collection = new DependencyObjectCollection(); +collection.Add(new DependencyObject("app", new[] { "database", "cache" })); +collection.Add(new DependencyObject("database", new[] { "config" })); +collection.Add(new DependencyObject("cache", new[] { "config" })); +collection.Add(new DependencyObject("config", Array.Empty())); + +// Resolve all dependencies for an element +var resolver = new DependencyResolver(collection); +var deps = resolver.Resolve("app"); // ["database", "cache", "config"] + +// Sort by dependency order +var sorter = new DependencySorter(collection); +var sorted = sorter.Sort(); // config, database, cache, app +``` + +## API Reference + +| Namespace | Purpose | +|-----------|---------| +| `CreativeCoders.Core` | Core validation (`Ensure`), extensions, disposables, fluent helpers | +| `CreativeCoders.Core.Caching` | In-memory cache with expiration and region support | +| `CreativeCoders.Core.Chaining` | Chain of Responsibility pattern | +| `CreativeCoders.Core.Collections` | LINQ extensions, observable collections, list/dictionary helpers | +| `CreativeCoders.Core.Comparing` | Function-based comparers and multi-key equality | +| `CreativeCoders.Core.Dependencies` | Dependency graph resolution and sorting | +| `CreativeCoders.Core.Enums` | Enum-to-string conversion and flag enumeration | +| `CreativeCoders.Core.EnsureArguments` | Fluent argument validation with `Argument` | +| `CreativeCoders.Core.Error` | Error handler abstraction | +| `CreativeCoders.Core.IO` | File system extensions, path safety, directory helpers | +| `CreativeCoders.Core.Messaging` | Pub/Sub messenger with weak references | +| `CreativeCoders.Core.ObjectLinking` | Bi-directional property binding between objects | +| `CreativeCoders.Core.Placeholders` | Template string placeholder replacement | +| `CreativeCoders.Core.Reflection` | Type discovery, generic instance creation, expression utilities | +| `CreativeCoders.Core.SysEnvironment` | Testable `System.Environment` abstraction | +| `CreativeCoders.Core.Text` | String extensions, pattern matching, JSON serialization | +| `CreativeCoders.Core.Threading` | Thread-safe collections, synchronized values, locking mechanisms | +| `CreativeCoders.Core.Visitors` | Visitor pattern infrastructure | +| `CreativeCoders.Core.Weak` | Weak reference delegates | From 7db0e3ff235648c150f34561db583543d86c08f6 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:39:45 +0200 Subject: [PATCH 2/5] Move `README.md` to `CreativeCoders.Core` folder to align with project structure. --- source/Core/CreativeCoders.Core/CreativeCoders.Core.csproj | 5 +++++ source/Core/{ => CreativeCoders.Core}/README.md | 0 2 files changed, 5 insertions(+) rename source/Core/{ => CreativeCoders.Core}/README.md (100%) diff --git a/source/Core/CreativeCoders.Core/CreativeCoders.Core.csproj b/source/Core/CreativeCoders.Core/CreativeCoders.Core.csproj index 4e33f226..8720768a 100644 --- a/source/Core/CreativeCoders.Core/CreativeCoders.Core.csproj +++ b/source/Core/CreativeCoders.Core/CreativeCoders.Core.csproj @@ -3,8 +3,13 @@ Library Basic core classes and types + README.md + + + + diff --git a/source/Core/README.md b/source/Core/CreativeCoders.Core/README.md similarity index 100% rename from source/Core/README.md rename to source/Core/CreativeCoders.Core/README.md From 10a70226b933d4621692d1b2ec7aca58de09da40 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:42:40 +0200 Subject: [PATCH 3/5] Add `README.md` for `CreativeCoders.Cli` with detailed documentation and examples - Introduced a comprehensive `README.md` covering installation, usage, and key features of the `CreativeCoders.Cli` framework. - Provided examples for commands, options, groups, dependency injection, and customization. - Enhanced XML documentation across the CLI codebase for better API clarity and IDE support. Updated solution/project to include the `README.md`. --- Core.sln | 3 + .../CliCommandAttribute.cs | 20 ++ .../CliCommandContext.cs | 5 + .../CliCommandGroupAttribute.cs | 13 + .../CliProcessorExecutionCondition.cs | 14 + .../Cli/CreativeCoders.Cli.Core/CliResult.cs | 8 + .../CreativeCoders.Cli.Core/CommandResult.cs | 16 + .../CreativeCoders.Cli.Core/ICliCommand.cs | 16 + .../ICliPostProcessor.cs | 12 + .../ICliPreProcessor.cs | 12 + .../CliExitCodes.cs | 24 ++ .../CliHostBuilder.cs | 7 + .../CliHostBuilderExtensions.cs | 2 +- .../CliHostSettings.cs | 7 + .../CliHostingServiceCollectionExtensions.cs | 7 + .../Commands/AssemblyCommandScanner.cs | 6 + .../Commands/AssemblyScanResult.cs | 11 + .../Commands/CliCommandInfo.cs | 15 + .../Commands/CommandInfoCreator.cs | 5 + .../Commands/IAssemblyCommandScanner.cs | 9 + .../Commands/ICommandInfoCreator.cs | 8 + .../Commands/Store/CliCommandGroupNode.cs | 9 + .../Commands/Store/CliCommandNode.cs | 10 + .../Commands/Store/CliCommandStore.cs | 9 + .../Commands/Store/CliTreeNode.cs | 21 ++ .../Commands/Store/FindCommandNodeResult.cs | 14 + .../Commands/Store/ICliCommandStore.cs | 30 ++ .../CliCommandStructureValidator.cs | 5 + .../ICliCommandStructureValidator.cs | 9 + .../DefaultCliHost.cs | 11 + .../DefaultCliHostBuilder.cs | 13 + .../AmbiguousCliCommandsException.cs | 3 + .../Exceptions/CliCommandAbortException.cs | 14 + .../CliCommandConstructionFailedException.cs | 10 + .../CliCommandGroupDuplicateException.cs | 3 + .../Exceptions/CliCommandNotFoundException.cs | 9 + .../CliCommandOptionsInvalidException.cs | 8 + .../CliCommandStructureValidationException.cs | 4 + .../Exceptions/CliExitException.cs | 10 + .../Exceptions/CliPostProcessorException.cs | 10 + .../Exceptions/CliPreProcessorException.cs | 10 + .../Help/CliCommandHelpHandler.cs | 11 + .../Help/DisabledCommandHelpHandler.cs | 6 + .../Help/HelpCommandKind.cs | 18 ++ .../Help/HelpHandlerSettings.cs | 7 + .../Help/ICliCommandHelpHandler.cs | 16 + .../PrintFooterPostProcessor.cs | 16 +- .../PrintHeaderPreProcessor.cs | 16 +- source/Cli/README.md | 306 ++++++++++++++++++ .../CliCommandStructureValidatorTests.cs | 32 ++ 50 files changed, 857 insertions(+), 3 deletions(-) rename source/Cli/CreativeCoders.Cli.Hosting/{PreProcessors => Processors}/PrintFooterPostProcessor.cs (53%) rename source/Cli/CreativeCoders.Cli.Hosting/{PreProcessors => Processors}/PrintHeaderPreProcessor.cs (53%) create mode 100644 source/Cli/README.md diff --git a/Core.sln b/Core.sln index f2aa3abe..bfff9a72 100644 --- a/Core.sln +++ b/Core.sln @@ -233,6 +233,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessUtilsSampleApp", "samples\ProcessUtilsSampleApp\ProcessUtilsSampleApp.csproj", "{58E94E6E-9A9A-4E3F-ACB5-89E47CE67C39}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cli", "Cli", "{CDCF2469-1668-4EB0-A73F-692115CF6776}" + ProjectSection(SolutionItems) = preProject + source\Cli\README.md = source\Cli\README.md + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreativeCoders.Cli.Core", "source\Cli\CreativeCoders.Cli.Core\CreativeCoders.Cli.Core.csproj", "{4E628A1A-953A-4274-B4EF-F33E943A650D}" EndProject diff --git a/source/Cli/CreativeCoders.Cli.Core/CliCommandAttribute.cs b/source/Cli/CreativeCoders.Cli.Core/CliCommandAttribute.cs index f19a08ee..b72c4b1d 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CliCommandAttribute.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CliCommandAttribute.cs @@ -2,14 +2,34 @@ namespace CreativeCoders.Cli.Core; +/// +/// Marks a class as a CLI command and specifies the command path used to invoke it. +/// +/// The command path segments used to invoke this command. [AttributeUsage(AttributeTargets.Class)] public class CliCommandAttribute(string[] commands) : Attribute { + /// + /// Gets or sets the display name of the command. + /// + /// The display name of the command. The default is . public string Name { get; set; } = string.Empty; + /// + /// Gets the command path segments used to invoke this command. + /// + /// An array of strings representing the command path. public string[] Commands { get; } = Ensure.NotNull(commands); + /// + /// Gets or sets the description of the command displayed in help output. + /// + /// The description text. The default is . public string Description { get; set; } = string.Empty; + /// + /// Gets or sets the alternative command path segments that can also invoke this command. + /// + /// An array of alternative command path segments. The default is an empty array. public string[] AlternativeCommands { get; init; } = []; } diff --git a/source/Cli/CreativeCoders.Cli.Core/CliCommandContext.cs b/source/Cli/CreativeCoders.Cli.Core/CliCommandContext.cs index e5faf3b3..5f0eb6e3 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CliCommandContext.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CliCommandContext.cs @@ -2,10 +2,15 @@ namespace CreativeCoders.Cli.Core; +/// +/// Provides the default implementation of . +/// [PublicAPI] public class CliCommandContext : ICliCommandContext { + /// public string[] AllArgs { get; set; } = []; + /// public string[] OptionsArgs { get; set; } = []; } diff --git a/source/Cli/CreativeCoders.Cli.Core/CliCommandGroupAttribute.cs b/source/Cli/CreativeCoders.Cli.Core/CliCommandGroupAttribute.cs index d77f49ab..e48c52ac 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CliCommandGroupAttribute.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CliCommandGroupAttribute.cs @@ -1,9 +1,22 @@ namespace CreativeCoders.Cli.Core; +/// +/// Defines a command group that organizes related CLI commands under a common path. +/// +/// The command path segments that identify this group. +/// The description of the command group displayed in help output. [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public class CliCommandGroupAttribute(string[] commands, string description) : Attribute { + /// + /// Gets the command path segments that identify this group. + /// + /// An array of strings representing the group command path. public string[] Commands { get; } = commands; + /// + /// Gets the description of the command group displayed in help output. + /// + /// The description text. public string Description { get; } = description; } diff --git a/source/Cli/CreativeCoders.Cli.Core/CliProcessorExecutionCondition.cs b/source/Cli/CreativeCoders.Cli.Core/CliProcessorExecutionCondition.cs index 5b326e1a..60c5cc05 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CliProcessorExecutionCondition.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CliProcessorExecutionCondition.cs @@ -1,8 +1,22 @@ namespace CreativeCoders.Cli.Core; +/// +/// Specifies when a CLI pre-processor or post-processor should be executed. +/// public enum CliProcessorExecutionCondition { + /// + /// The processor is executed for every CLI invocation. + /// Always, + + /// + /// The processor is executed only when help output is displayed. + /// OnlyOnHelp, + + /// + /// The processor is executed only when a CLI command is run. + /// OnlyOnCommand } diff --git a/source/Cli/CreativeCoders.Cli.Core/CliResult.cs b/source/Cli/CreativeCoders.Cli.Core/CliResult.cs index 5748f546..41a8a349 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CliResult.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CliResult.cs @@ -2,8 +2,16 @@ namespace CreativeCoders.Cli.Core; +/// +/// Represents the result of a CLI application execution. +/// +/// The exit code of the CLI execution. [PublicAPI] public class CliResult(int exitCode) { + /// + /// Gets or sets the exit code of the CLI execution. + /// + /// The exit code. public int ExitCode { get; set; } = exitCode; } diff --git a/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs b/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs index ca1e8e15..b18c1160 100644 --- a/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs +++ b/source/Cli/CreativeCoders.Cli.Core/CommandResult.cs @@ -13,15 +13,31 @@ public class CommandResult /// public static CommandResult Success { get; } = new CommandResult(); + /// + /// Initializes a new instance of the class with an exit code of 0. + /// public CommandResult() { } + /// + /// Initializes a new instance of the class with the specified exit code. + /// + /// The exit code for the command result. public CommandResult(int exitCode) { ExitCode = exitCode; } + /// + /// Gets the exit code of the command execution. + /// + /// The exit code. The default is 0. public int ExitCode { get; init; } + /// + /// Implicitly converts an integer exit code to a . + /// + /// The exit code to convert. + /// A representing the exit code. Returns if the exit code is 0. public static implicit operator CommandResult(int exitCode) => exitCode == 0 ? Success diff --git a/source/Cli/CreativeCoders.Cli.Core/ICliCommand.cs b/source/Cli/CreativeCoders.Cli.Core/ICliCommand.cs index 36974fb7..dfc797a3 100644 --- a/source/Cli/CreativeCoders.Cli.Core/ICliCommand.cs +++ b/source/Cli/CreativeCoders.Cli.Core/ICliCommand.cs @@ -1,12 +1,28 @@ namespace CreativeCoders.Cli.Core; +/// +/// Defines a CLI command that accepts options of type . +/// +/// The type of the options passed to the command. public interface ICliCommand where TOptions : class { + /// + /// Executes the CLI command asynchronously with the specified options. + /// + /// The options for the command. + /// A representing the outcome of the command execution. Task ExecuteAsync(TOptions options); } +/// +/// Defines a CLI command without options. +/// public interface ICliCommand { + /// + /// Executes the CLI command asynchronously. + /// + /// A representing the outcome of the command execution. Task ExecuteAsync(); } diff --git a/source/Cli/CreativeCoders.Cli.Core/ICliPostProcessor.cs b/source/Cli/CreativeCoders.Cli.Core/ICliPostProcessor.cs index 7348bf0c..6e188147 100644 --- a/source/Cli/CreativeCoders.Cli.Core/ICliPostProcessor.cs +++ b/source/Cli/CreativeCoders.Cli.Core/ICliPostProcessor.cs @@ -2,10 +2,22 @@ namespace CreativeCoders.Cli.Core; +/// +/// Defines a post-processor that executes after a CLI command has completed. +/// [PublicAPI] public interface ICliPostProcessor { + /// + /// Executes the post-processor asynchronously with the result of the CLI command. + /// + /// The result of the CLI command execution. + /// A representing the asynchronous operation. Task ExecuteAsync(CliResult cliResult); + /// + /// Gets the condition that determines when this post-processor is executed. + /// + /// One of the enumeration values that specifies the execution condition. CliProcessorExecutionCondition ExecutionCondition { get; } } diff --git a/source/Cli/CreativeCoders.Cli.Core/ICliPreProcessor.cs b/source/Cli/CreativeCoders.Cli.Core/ICliPreProcessor.cs index 29a5bc6a..ae796b23 100644 --- a/source/Cli/CreativeCoders.Cli.Core/ICliPreProcessor.cs +++ b/source/Cli/CreativeCoders.Cli.Core/ICliPreProcessor.cs @@ -2,10 +2,22 @@ namespace CreativeCoders.Cli.Core; +/// +/// Defines a pre-processor that executes before a CLI command is processed. +/// [PublicAPI] public interface ICliPreProcessor { + /// + /// Executes the pre-processor asynchronously with the provided command line arguments. + /// + /// The command line arguments passed to the CLI application. + /// A representing the asynchronous operation. Task ExecuteAsync(string[] args); + /// + /// Gets the condition that determines when this pre-processor is executed. + /// + /// One of the enumeration values that specifies the execution condition. CliProcessorExecutionCondition ExecutionCondition { get; } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs b/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs index b7461b6c..42e16051 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/CliExitCodes.cs @@ -1,18 +1,42 @@ namespace CreativeCoders.Cli.Hosting; +/// +/// Defines well-known exit codes used by the CLI hosting infrastructure. +/// public static class CliExitCodes { + /// + /// The exit code indicating successful execution. + /// public const int Success = 0; + /// + /// The exit code indicating that no matching command was found for the given arguments. + /// public const int CommandNotFound = int.MinValue; + /// + /// The exit code indicating that command creation failed. + /// public const int CommandCreationFailed = int.MinValue + 1; + /// + /// The exit code indicating that the command result could not be determined. + /// public const int CommandResultUnknown = int.MinValue + 2; + /// + /// The exit code indicating that the command options failed validation. + /// public const int CommandOptionsInvalid = int.MinValue + 3; + /// + /// The exit code indicating that a pre-processor failed during execution. + /// public const int PreProcessorFailed = int.MinValue + 4; + /// + /// The exit code indicating that a post-processor failed during execution. + /// public const int PostProcessorFailed = int.MinValue + 5; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilder.cs b/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilder.cs index c5da5924..d5e7feb5 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilder.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilder.cs @@ -2,9 +2,16 @@ namespace CreativeCoders.Cli.Hosting; +/// +/// Provides a static factory for creating instances. +/// [ExcludeFromCodeCoverage] public static class CliHostBuilder { + /// + /// Creates a new instance of the default . + /// + /// A new instance. public static ICliHostBuilder Create() { return new DefaultCliHostBuilder(); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilderExtensions.cs b/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilderExtensions.cs index 21bfb4f7..43a7a00b 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilderExtensions.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/CliHostBuilderExtensions.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; using CreativeCoders.Cli.Core; -using CreativeCoders.Cli.Hosting.PreProcessors; +using CreativeCoders.Cli.Hosting.Processors; namespace CreativeCoders.Cli.Hosting; diff --git a/source/Cli/CreativeCoders.Cli.Hosting/CliHostSettings.cs b/source/Cli/CreativeCoders.Cli.Hosting/CliHostSettings.cs index f8ea4728..8ff36cef 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/CliHostSettings.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/CliHostSettings.cs @@ -1,6 +1,13 @@ namespace CreativeCoders.Cli.Hosting; +/// +/// Holds configuration settings for the CLI host. +/// public class CliHostSettings { + /// + /// Gets a value indicating whether command options validation is enabled. + /// + /// if validation is enabled; otherwise, . The default is . public bool UseValidation { get; init; } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/CliHostingServiceCollectionExtensions.cs b/source/Cli/CreativeCoders.Cli.Hosting/CliHostingServiceCollectionExtensions.cs index bb66657c..05b354f7 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/CliHostingServiceCollectionExtensions.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/CliHostingServiceCollectionExtensions.cs @@ -8,8 +8,15 @@ namespace CreativeCoders.Cli.Hosting; +/// +/// Provides extension methods for registering CLI hosting services in an . +/// public static class CliHostingServiceCollectionExtensions { + /// + /// Registers the required CLI hosting services in the service collection. + /// + /// The service collection to add the CLI hosting services to. public static void AddCliHosting(this IServiceCollection services) { services.TryAddSingleton(); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyCommandScanner.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyCommandScanner.cs index 2df6844b..28dbfb27 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyCommandScanner.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyCommandScanner.cs @@ -5,10 +5,16 @@ namespace CreativeCoders.Cli.Hosting.Commands; +/// +/// Provides the default implementation of that discovers +/// CLI commands by scanning assemblies for types decorated with . +/// +/// The creator used to build command info from discovered types. public class AssemblyCommandScanner(ICommandInfoCreator commandInfoCreator) : IAssemblyCommandScanner { private readonly ICommandInfoCreator _commandInfoCreator = Ensure.NotNull(commandInfoCreator); + /// public AssemblyScanResult ScanForCommands(Assembly[] assemblies, Func? predicate = null) { var commandInfos = assemblies diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyScanResult.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyScanResult.cs index 8e8c7237..7b1357a3 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyScanResult.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/AssemblyScanResult.cs @@ -2,9 +2,20 @@ namespace CreativeCoders.Cli.Hosting.Commands; +/// +/// Represents the result of scanning assemblies for CLI commands. +/// public class AssemblyScanResult { + /// + /// Gets the collection of discovered command information objects. + /// + /// An enumerable of instances. public required IEnumerable CommandInfos { get; init; } + /// + /// Gets the collection of command group attributes found in the scanned assemblies. + /// + /// An enumerable of instances. public required IEnumerable GroupAttributes { get; init; } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/CliCommandInfo.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/CliCommandInfo.cs index 97c469f9..046a58ee 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/CliCommandInfo.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/CliCommandInfo.cs @@ -2,11 +2,26 @@ namespace CreativeCoders.Cli.Hosting.Commands; +/// +/// Holds metadata about a registered CLI command, including its attribute, type, and options type. +/// public class CliCommandInfo { + /// + /// Gets the attribute that defines the command's name, path, and description. + /// + /// The applied to the command class. public required CliCommandAttribute CommandAttribute { get; init; } + /// + /// Gets the type of the command class. + /// + /// The of the CLI command implementation. public required Type CommandType { get; init; } + /// + /// Gets the type of the options class used by the command, if any. + /// + /// The of the options class, or if the command has no options. public Type? OptionsType { get; init; } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/CommandInfoCreator.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/CommandInfoCreator.cs index 34c79cee..9fe8e006 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/CommandInfoCreator.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/CommandInfoCreator.cs @@ -4,8 +4,13 @@ namespace CreativeCoders.Cli.Hosting.Commands; +/// +/// Provides the default implementation of that creates +/// instances by inspecting the on command types. +/// public class CommandInfoCreator : ICommandInfoCreator { + /// public CliCommandInfo? Create(Type commandType) { var commandAttribute = commandType.GetCustomAttribute(false); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/IAssemblyCommandScanner.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/IAssemblyCommandScanner.cs index ac5eebdd..708bcb28 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/IAssemblyCommandScanner.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/IAssemblyCommandScanner.cs @@ -2,7 +2,16 @@ namespace CreativeCoders.Cli.Hosting.Commands; +/// +/// Defines a scanner that discovers CLI commands by scanning assemblies. +/// public interface IAssemblyCommandScanner { + /// + /// Scans the specified assemblies for CLI commands decorated with . + /// + /// The assemblies to scan for commands. + /// An optional predicate to filter discovered command types. + /// An containing the discovered commands and group attributes. AssemblyScanResult ScanForCommands(Assembly[] assemblies, Func? predicate = null); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/ICommandInfoCreator.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/ICommandInfoCreator.cs index c4b2d6a1..75b2597d 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/ICommandInfoCreator.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/ICommandInfoCreator.cs @@ -1,6 +1,14 @@ namespace CreativeCoders.Cli.Hosting.Commands; +/// +/// Defines a creator that builds instances from command types. +/// public interface ICommandInfoCreator { + /// + /// Creates a for the specified command type. + /// + /// The type of the CLI command to create info for. + /// A instance, or if the type is not a valid CLI command. CliCommandInfo? Create(Type commandType); } \ No newline at end of file diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandGroupNode.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandGroupNode.cs index debdf8f7..7523863a 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandGroupNode.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandGroupNode.cs @@ -2,8 +2,17 @@ namespace CreativeCoders.Cli.Hosting.Commands.Store; +/// +/// Represents a command group node in the CLI command tree that contains child commands or subgroups. +/// +/// The name of the command group. +/// The parent group node, or if this is a root group. public class CliCommandGroupNode(string groupName, CliCommandGroupNode? parent) : CliTreeNode(groupName, parent) { + /// + /// Gets or sets the attribute that provides metadata for this command group. + /// + /// The associated with this group, or if no attribute is defined. public CliCommandGroupAttribute? GroupAttribute { get; set; } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandNode.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandNode.cs index bbfab40b..64993e97 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandNode.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandNode.cs @@ -2,8 +2,18 @@ namespace CreativeCoders.Cli.Hosting.Commands.Store; +/// +/// Represents a leaf node in the CLI command tree that holds a specific command. +/// +/// The command information associated with this node. +/// The command name segment. +/// The parent group node, or if this is a root command. public class CliCommandNode(CliCommandInfo commandInfo, string command, CliCommandGroupNode? parent) : CliTreeNode(command, parent) { + /// + /// Gets the command information associated with this node. + /// + /// The for this command. public CliCommandInfo CommandInfo { get; } = Ensure.NotNull(commandInfo); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandStore.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandStore.cs index 361aca36..8506372f 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandStore.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliCommandStore.cs @@ -4,6 +4,9 @@ namespace CreativeCoders.Cli.Hosting.Commands.Store; +/// +/// Provides the default implementation of that organizes commands in a tree structure. +/// public class CliCommandStore : ICliCommandStore { private readonly List _commands = []; @@ -12,6 +15,7 @@ public class CliCommandStore : ICliCommandStore private IEnumerable? _groupAttributes; + /// public void AddCommands(IEnumerable commands, IEnumerable? groupAttributes = null) { @@ -22,6 +26,7 @@ public void AddCommands(IEnumerable commands, commands.ForEach(AddCommand); } + /// public FindCommandNodeResult? FindCommandGroupNode(string[] args) { return FindCommandGroupNode(null, _treeRootNodes, args); @@ -55,6 +60,7 @@ public void AddCommands(IEnumerable commands, }; } + /// public FindCommandNodeResult? FindCommandNode(string[] args) { return FindCommandNode(_treeRootNodes, args); @@ -151,9 +157,12 @@ private CliCommandGroupNode GetGroupNode(CliCommandGroupNode? parent, List public IEnumerable TreeRootNodes => _treeRootNodes; + /// public IEnumerable Commands => _commands; + /// public IEnumerable GroupAttributes => _groupAttributes ?? []; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliTreeNode.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliTreeNode.cs index 54293141..337af3a8 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliTreeNode.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/CliTreeNode.cs @@ -2,8 +2,17 @@ namespace CreativeCoders.Cli.Hosting.Commands.Store; +/// +/// Represents a node in the CLI command tree structure. +/// +/// The name of this tree node. +/// The parent group node, or if this is a root node. public class CliTreeNode(string name, CliCommandGroupNode? parent) { + /// + /// Returns the full name path from the root to this node. + /// + /// An enumerable of name segments from root to this node. public IEnumerable GetNamePath() { if (Parent != null) @@ -17,9 +26,21 @@ public IEnumerable GetNamePath() yield return Name; } + /// + /// Gets the child nodes of this tree node. + /// + /// A list of child instances. public List ChildNodes { get; } = []; + /// + /// Gets the parent group node. + /// + /// The parent , or if this is a root node. public CliCommandGroupNode? Parent { get; } = parent; + /// + /// Gets the name of this tree node. + /// + /// The node name. public string Name { get; } = Ensure.IsNotNullOrWhitespace(name); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/FindCommandNodeResult.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/FindCommandNodeResult.cs index 40cd94bc..beca9037 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/FindCommandNodeResult.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/FindCommandNodeResult.cs @@ -2,10 +2,24 @@ namespace CreativeCoders.Cli.Hosting.Commands.Store; +/// +/// Represents the result of searching for a node in the CLI command tree. +/// +/// The type of the tree node found. +/// The found tree node, or if no match was found. +/// The arguments that were not consumed during the search. public class FindCommandNodeResult(TNode? node, string[] remainingArgs) where TNode : CliTreeNode { + /// + /// Gets the found tree node. + /// + /// The matched , or if no match was found. public TNode? Node { get; } = node; + /// + /// Gets the arguments that were not consumed during the command search. + /// + /// An array of remaining argument strings. public string[] RemainingArgs { get; } = Ensure.NotNull(remainingArgs); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/ICliCommandStore.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/ICliCommandStore.cs index 9b32a67f..3cdcc7b5 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/ICliCommandStore.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Store/ICliCommandStore.cs @@ -2,19 +2,49 @@ namespace CreativeCoders.Cli.Hosting.Commands.Store; +/// +/// Defines a store for CLI commands organized in a tree structure. +/// public interface ICliCommandStore { + /// + /// Adds commands and optional group attributes to the store. + /// + /// The collection of command information objects to add. + /// An optional collection of group attributes for organizing commands. void AddCommands(IEnumerable commands, IEnumerable? groupAttributes = null); + /// + /// Searches for a command group node matching the provided arguments. + /// + /// The command line arguments to match against the command tree. + /// A containing the matched group node and remaining args, or if no match was found. FindCommandNodeResult? FindCommandGroupNode(string[] args); + /// + /// Searches for a command node matching the provided arguments. + /// + /// The command line arguments to match against the command tree. + /// A containing the matched command node and remaining args, or if no match was found. FindCommandNodeResult? FindCommandNode(string[] args); + /// + /// Gets the root nodes of the command tree. + /// + /// An enumerable of root instances. IEnumerable TreeRootNodes { get; } + /// + /// Gets all registered command information objects. + /// + /// An enumerable of instances. IEnumerable Commands { get; } + /// + /// Gets all registered command group attributes. + /// + /// An enumerable of instances. IEnumerable GroupAttributes { get; } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/CliCommandStructureValidator.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/CliCommandStructureValidator.cs index 02081c38..a1f3df56 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/CliCommandStructureValidator.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/CliCommandStructureValidator.cs @@ -3,8 +3,13 @@ namespace CreativeCoders.Cli.Hosting.Commands.Validation; +/// +/// Provides the default implementation of that checks +/// for duplicate group attributes and ambiguous command definitions. +/// public class CliCommandStructureValidator : ICliCommandStructureValidator { + /// public void Validate(ICliCommandStore commandStore) { // Ensure that all Group attributes commands are unique diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/ICliCommandStructureValidator.cs b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/ICliCommandStructureValidator.cs index ec35e085..2bb7fc2a 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/ICliCommandStructureValidator.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Commands/Validation/ICliCommandStructureValidator.cs @@ -2,7 +2,16 @@ namespace CreativeCoders.Cli.Hosting.Commands.Validation; +/// +/// Defines a validator for the CLI command tree structure. +/// public interface ICliCommandStructureValidator { + /// + /// Validates the command store structure for duplicates and ambiguous commands. + /// + /// The command store to validate. + /// Duplicate group attributes were found. + /// Ambiguous command definitions were found. void Validate(ICliCommandStore commandStore); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs index 05ade4f4..4f6eb84d 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHost.cs @@ -13,6 +13,16 @@ namespace CreativeCoders.Cli.Hosting; +/// +/// Provides the default implementation of that resolves and executes CLI commands +/// with pre-processing, post-processing, help handling, and error management. +/// +/// The console used for rendering output. +/// The command store containing all registered commands. +/// The service provider used for dependency resolution. +/// The handler responsible for displaying help output. +/// The collection of pre-processors to execute before commands. +/// The collection of post-processors to execute after commands. public class DefaultCliHost( IAnsiConsole ansiConsole, ICliCommandStore commandStore, @@ -106,6 +116,7 @@ private async Task ValidateCommandOptionsAsync(object options) } } + /// public async Task RunAsync(string[] args) { try diff --git a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs index 20445a44..93e05914 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/DefaultCliHostBuilder.cs @@ -12,6 +12,9 @@ namespace CreativeCoders.Cli.Hosting; +/// +/// Provides the default implementation of for configuring and building a CLI host. +/// public class DefaultCliHostBuilder : ICliHostBuilder { private readonly List _scanAssemblies = []; @@ -28,6 +31,7 @@ public class DefaultCliHostBuilder : ICliHostBuilder private bool _useValidation; + /// public ICliHostBuilder UseContext(Action? configure = null) where TContext : class, ICliCommandContext { @@ -50,6 +54,7 @@ public ICliHostBuilder UseContext(Action? .AddSingleton()); } + /// public ICliHostBuilder ConfigureServices(Action configureServices) { Ensure.NotNull(configureServices); @@ -59,6 +64,7 @@ public ICliHostBuilder ConfigureServices(Action configureSer return this; } + /// public ICliHostBuilder ScanAssemblies(params Assembly[] assemblies) { _scanAssemblies.AddRange(assemblies); @@ -66,6 +72,7 @@ public ICliHostBuilder ScanAssemblies(params Assembly[] assemblies) return this; } + /// public ICliHostBuilder EnableHelp(params HelpCommandKind[] commandKinds) { _helpEnabled = true; @@ -74,6 +81,7 @@ public ICliHostBuilder EnableHelp(params HelpCommandKind[] commandKinds) return this; } + /// public ICliHostBuilder UseValidation(bool useValidation = true) { _useValidation = useValidation; @@ -81,6 +89,7 @@ public ICliHostBuilder UseValidation(bool useValidation = true) return this; } + /// public ICliHostBuilder SkipScanEntryAssembly(bool skipScanEntryAssembly = true) { _skipScanEntryAssembly = skipScanEntryAssembly; @@ -88,6 +97,7 @@ public ICliHostBuilder SkipScanEntryAssembly(bool skipScanEntryAssembly = true) return this; } + /// public ICliHostBuilder RegisterPreProcessor(Action? configure = null) where T : class, ICliPreProcessor { @@ -106,6 +116,7 @@ public ICliHostBuilder RegisterPreProcessor(Action? configure = null) return ConfigureServices(x => x.AddSingleton()); } + /// public ICliHostBuilder RegisterPostProcessor(Action? configure = null) where T : class, ICliPostProcessor { @@ -124,6 +135,7 @@ public ICliHostBuilder RegisterPostProcessor(Action? configure = null) return ConfigureServices(x => x.AddSingleton()); } + /// public ICliHostBuilder UseConfiguration(Action configure) { Ensure.NotNull(configure); @@ -191,6 +203,7 @@ private void ScanEntryAssemblyIfNecessary() } } + /// public ICliHost Build() { ScanEntryAssemblyIfNecessary(); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/AmbiguousCliCommandsException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/AmbiguousCliCommandsException.cs index 2b269928..ce29a2c6 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/AmbiguousCliCommandsException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/AmbiguousCliCommandsException.cs @@ -1,4 +1,7 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when ambiguous CLI command definitions are detected during validation. +/// public class AmbiguousCliCommandsException() : CliCommandStructureValidationException("Ambiguous commands found"); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandAbortException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandAbortException.cs index fef13f6f..d2dc2f4a 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandAbortException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandAbortException.cs @@ -2,11 +2,25 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when a CLI command is intentionally aborted. +/// +/// The abort message. +/// The exit code to return. +/// The optional inner exception. [PublicAPI] public class CliCommandAbortException(string message, int exitCode, Exception? exception = null) : CliExitException(message, exitCode, exception) { + /// + /// Gets or sets a value indicating whether this abort is treated as an error. + /// + /// if the abort is an error; otherwise, . The default is . public bool IsError { get; set; } = true; + /// + /// Gets or sets a value indicating whether the abort message should be printed to the console. + /// + /// if the message should be printed; otherwise, . The default is . public bool PrintMessage { get; set; } = true; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandConstructionFailedException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandConstructionFailedException.cs index 0f45bb53..1abc7819 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandConstructionFailedException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandConstructionFailedException.cs @@ -2,6 +2,12 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when a CLI command could not be constructed from the service provider. +/// +/// The error message. +/// The command line arguments that were being processed. +/// The optional inner exception that caused the construction failure. [PublicAPI] public class CliCommandConstructionFailedException( string message, @@ -9,5 +15,9 @@ public class CliCommandConstructionFailedException( Exception? exception = null) : CliExitException(message, CliExitCodes.CommandCreationFailed, exception) { + /// + /// Gets the command line arguments that were being processed when construction failed. + /// + /// An array of argument strings. public string[] Args { get; } = args; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandGroupDuplicateException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandGroupDuplicateException.cs index cfd7cd00..925c7df2 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandGroupDuplicateException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandGroupDuplicateException.cs @@ -1,4 +1,7 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when duplicate command group attributes are detected during validation. +/// public class CliCommandGroupDuplicateException() : CliCommandStructureValidationException("Group attribute duplicates found"); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandNotFoundException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandNotFoundException.cs index 8a63a802..1ec11171 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandNotFoundException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandNotFoundException.cs @@ -2,9 +2,18 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when no CLI command matches the provided arguments. +/// +/// The error message. +/// The command line arguments that did not match any command. [PublicAPI] public class CliCommandNotFoundException(string message, string[] args) : CliExitException(message, CliExitCodes.CommandNotFound) { + /// + /// Gets the command line arguments that did not match any registered command. + /// + /// An array of unmatched argument strings. public string[] Args { get; } = args; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs index 6deae8a3..2444deca 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandOptionsInvalidException.cs @@ -3,8 +3,16 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when command options fail validation. +/// +/// The validation result containing the failure details. public class CliCommandOptionsInvalidException(OptionsValidationResult validationResult) : CliExitException("Command options are invalid", CliExitCodes.CommandOptionsInvalid) { + /// + /// Gets the validation result containing the failure messages. + /// + /// The describing the validation failures. public OptionsValidationResult ValidationResult { get; } = Ensure.NotNull(validationResult); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandStructureValidationException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandStructureValidationException.cs index 7a6a67f7..4c46fe1a 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandStructureValidationException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliCommandStructureValidationException.cs @@ -1,3 +1,7 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when the CLI command structure validation fails. +/// +/// The validation error message. public class CliCommandStructureValidationException(string message) : Exception(message); diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliExitException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliExitException.cs index 78bf96f2..51dfd6f5 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliExitException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliExitException.cs @@ -1,7 +1,17 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents a CLI exception that causes the application to exit with a specific exit code. +/// +/// The error message. +/// The exit code to return. +/// The optional inner exception. public class CliExitException(string message, int exitCode, Exception? exception = null) : Exception(message, exception) { + /// + /// Gets the exit code associated with this exception. + /// + /// The exit code. public int ExitCode { get; } = exitCode; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPostProcessorException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPostProcessorException.cs index c76898f7..9a9aa15b 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPostProcessorException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPostProcessorException.cs @@ -2,10 +2,20 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when a CLI post-processor fails during execution. +/// +/// The post-processor that caused the failure. +/// The error message. +/// The inner exception that caused the failure. public class CliPostProcessorException( ICliPostProcessor postProcessor, string message, Exception? innerException) : CliExitException(message, CliExitCodes.PostProcessorFailed, innerException) { + /// + /// Gets the post-processor that caused the failure. + /// + /// The instance that failed. public ICliPostProcessor PostProcessor { get; } = postProcessor; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPreProcessorException.cs b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPreProcessorException.cs index a0d6ceff..0e297235 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPreProcessorException.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Exceptions/CliPreProcessorException.cs @@ -2,11 +2,21 @@ namespace CreativeCoders.Cli.Hosting.Exceptions; +/// +/// Represents an exception thrown when a CLI pre-processor fails during execution. +/// +/// The pre-processor that caused the failure. +/// The error message. +/// The inner exception that caused the failure. public class CliPreProcessorException( ICliPreProcessor preProcessor, string message, Exception? innerException) : CliExitException(message, CliExitCodes.PreProcessorFailed, innerException) { + /// + /// Gets the pre-processor that caused the failure. + /// + /// The instance that failed. public ICliPreProcessor PreProcessor { get; } = preProcessor; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Help/CliCommandHelpHandler.cs b/source/Cli/CreativeCoders.Cli.Hosting/Help/CliCommandHelpHandler.cs index 6c6e67db..5d8a253c 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Help/CliCommandHelpHandler.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Help/CliCommandHelpHandler.cs @@ -9,6 +9,14 @@ namespace CreativeCoders.Cli.Hosting.Help; +/// +/// Provides the default implementation of +/// that renders help output to the console using Spectre.Console. +/// +/// The help handler configuration settings. +/// The command store containing all registered commands. +/// The console used for rendering output. +/// The generator for options help text. public class CliCommandHelpHandler( HelpHandlerSettings settings, ICliCommandStore commandStore, @@ -24,6 +32,7 @@ public class CliCommandHelpHandler( private readonly HelpHandlerSettings _settings = Ensure.NotNull(settings); + /// public bool ShouldPrintHelp(string[] args) { var lowerCaseArgs = args.Select(x => x.ToLower()).ToArray(); @@ -44,6 +53,7 @@ private static bool ShouldPrintHelpFor(HelpCommandKind helpCommandKind, string[] }; } + /// public void PrintHelp(string[] args) { if (args.FirstOrDefault()?.ToLower() == "help") @@ -72,6 +82,7 @@ public void PrintHelp(string[] args) PrintHelpFor(_commandStore.TreeRootNodes.ToList()); } + /// public void PrintHelpFor(IList nodeChildNodes) { if (nodeChildNodes.Count == 0) diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Help/DisabledCommandHelpHandler.cs b/source/Cli/CreativeCoders.Cli.Hosting/Help/DisabledCommandHelpHandler.cs index 4a6a56c1..d4780527 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Help/DisabledCommandHelpHandler.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Help/DisabledCommandHelpHandler.cs @@ -3,12 +3,18 @@ namespace CreativeCoders.Cli.Hosting.Help; +/// +/// Provides a no-op implementation of used when help is disabled. +/// [ExcludeFromCodeCoverage] public class DisabledCommandHelpHandler : ICliCommandHelpHandler { + /// public bool ShouldPrintHelp(string[] args) => false; + /// public void PrintHelp(string[] args) { } + /// public void PrintHelpFor(IList nodeChildNodes) { } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpCommandKind.cs b/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpCommandKind.cs index 169bcfb9..e7ae8c05 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpCommandKind.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpCommandKind.cs @@ -1,9 +1,27 @@ namespace CreativeCoders.Cli.Hosting.Help; +/// +/// Defines the types of help command invocation supported by the CLI application. +/// public enum HelpCommandKind { + /// + /// Help is invoked using the help command as the first argument. + /// Command, + + /// + /// Help is invoked using the --help argument. + /// Argument, + + /// + /// Help is displayed when no arguments are provided. + /// EmptyArgs, + + /// + /// Help is invoked using either the help command or the --help argument. + /// CommandOrArgument } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpHandlerSettings.cs b/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpHandlerSettings.cs index 3a47678e..6baca5e4 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpHandlerSettings.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Help/HelpHandlerSettings.cs @@ -1,6 +1,13 @@ namespace CreativeCoders.Cli.Hosting.Help; +/// +/// Holds the configuration settings for the help handler. +/// public class HelpHandlerSettings { + /// + /// Gets the help command kinds that trigger help output. + /// + /// An array of values. The default is an empty array. public HelpCommandKind[] CommandKinds { get; init; } = []; } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/Help/ICliCommandHelpHandler.cs b/source/Cli/CreativeCoders.Cli.Hosting/Help/ICliCommandHelpHandler.cs index 7c4876b7..1296d88c 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/Help/ICliCommandHelpHandler.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Help/ICliCommandHelpHandler.cs @@ -2,11 +2,27 @@ namespace CreativeCoders.Cli.Hosting.Help; +/// +/// Defines a handler for printing CLI command help information. +/// public interface ICliCommandHelpHandler { + /// + /// Determines whether help should be printed based on the provided arguments. + /// + /// The command line arguments. + /// if help should be printed; otherwise, . bool ShouldPrintHelp(string[] args); + /// + /// Prints help output based on the provided arguments. + /// + /// The command line arguments used to determine which help to display. void PrintHelp(string[] args); + /// + /// Prints help output for the specified collection of tree nodes. + /// + /// The list of CLI tree nodes to display help for. void PrintHelpFor(IList nodeChildNodes); } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/PreProcessors/PrintFooterPostProcessor.cs b/source/Cli/CreativeCoders.Cli.Hosting/Processors/PrintFooterPostProcessor.cs similarity index 53% rename from source/Cli/CreativeCoders.Cli.Hosting/PreProcessors/PrintFooterPostProcessor.cs rename to source/Cli/CreativeCoders.Cli.Hosting/Processors/PrintFooterPostProcessor.cs index 1153f950..5cb4e901 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/PreProcessors/PrintFooterPostProcessor.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Processors/PrintFooterPostProcessor.cs @@ -4,14 +4,19 @@ using JetBrains.Annotations; using Spectre.Console; -namespace CreativeCoders.Cli.Hosting.PreProcessors; +namespace CreativeCoders.Cli.Hosting.Processors; +/// +/// Prints footer lines to the console after command execution as a CLI post-processor. +/// +/// The console used for rendering output. [UsedImplicitly] [ExcludeFromCodeCoverage] public class PrintFooterPostProcessor(IAnsiConsole ansiConsole) : ICliPostProcessor { private readonly IAnsiConsole _ansiConsole = Ensure.NotNull(ansiConsole); + /// public Task ExecuteAsync(CliResult cliResult) { if (PlainText) @@ -32,9 +37,18 @@ public Task ExecuteAsync(CliResult cliResult) return Task.CompletedTask; } + /// public CliProcessorExecutionCondition ExecutionCondition { get; set; } + /// + /// Gets or sets the lines of text to display in the footer. + /// + /// An enumerable collection of footer lines. The default is an empty collection. public IEnumerable Lines { get; set; } = []; + /// + /// Gets or sets a value indicating whether the lines are rendered as plain text. + /// + /// if the lines are rendered as plain text; otherwise, to render as markup. The default is . public bool PlainText { get; set; } } diff --git a/source/Cli/CreativeCoders.Cli.Hosting/PreProcessors/PrintHeaderPreProcessor.cs b/source/Cli/CreativeCoders.Cli.Hosting/Processors/PrintHeaderPreProcessor.cs similarity index 53% rename from source/Cli/CreativeCoders.Cli.Hosting/PreProcessors/PrintHeaderPreProcessor.cs rename to source/Cli/CreativeCoders.Cli.Hosting/Processors/PrintHeaderPreProcessor.cs index 4feefd99..2a48bf36 100644 --- a/source/Cli/CreativeCoders.Cli.Hosting/PreProcessors/PrintHeaderPreProcessor.cs +++ b/source/Cli/CreativeCoders.Cli.Hosting/Processors/PrintHeaderPreProcessor.cs @@ -4,14 +4,19 @@ using JetBrains.Annotations; using Spectre.Console; -namespace CreativeCoders.Cli.Hosting.PreProcessors; +namespace CreativeCoders.Cli.Hosting.Processors; +/// +/// Prints header lines to the console before command execution as a CLI pre-processor. +/// +/// The console used for rendering output. [UsedImplicitly] [ExcludeFromCodeCoverage] public class PrintHeaderPreProcessor(IAnsiConsole ansiConsole) : ICliPreProcessor { private readonly IAnsiConsole _ansiConsole = Ensure.NotNull(ansiConsole); + /// public Task ExecuteAsync(string[] args) { if (PlainText) @@ -32,9 +37,18 @@ public Task ExecuteAsync(string[] args) return Task.CompletedTask; } + /// public CliProcessorExecutionCondition ExecutionCondition { get; set; } + /// + /// Gets or sets the lines of text to display in the header. + /// + /// An enumerable collection of header lines. The default is an empty collection. public IEnumerable Lines { get; set; } = []; + /// + /// Gets or sets a value indicating whether the lines are rendered as plain text. + /// + /// if the lines are rendered as plain text; otherwise, to render as markup. The default is . public bool PlainText { get; set; } } diff --git a/source/Cli/README.md b/source/Cli/README.md new file mode 100644 index 00000000..57fdaca8 --- /dev/null +++ b/source/Cli/README.md @@ -0,0 +1,306 @@ +# CreativeCoders.Cli + +A lightweight, convention-based CLI hosting framework for .NET that lets you build command-line applications with structured commands, automatic help generation, and dependency injection — all with minimal boilerplate. + +## Packages + +| Package | Description | +|---|---| +| `CreativeCoders.Cli.Core` | Core abstractions: command interfaces, attributes, and result types | +| `CreativeCoders.Cli.Hosting` | Hosting infrastructure: builder, command discovery, help, pre/post-processors | + +## Getting started + +### Installation + +```bash +dotnet add package CreativeCoders.Cli.Core +dotnet add package CreativeCoders.Cli.Hosting +``` + +### Minimal example + +Create a CLI host in your `Program.cs`: + +```csharp +using CreativeCoders.Cli.Hosting; +using CreativeCoders.Cli.Hosting.Help; + +await CliHostBuilder.Create() + .EnableHelp(HelpCommandKind.CommandOrArgument) + .Build() + .RunMainAsync(args); +``` + +Define a command: + +```csharp +using CreativeCoders.Cli.Core; + +[CliCommand(["greet"], Name = "greet", Description = "Prints a greeting")] +public class GreetCommand : ICliCommand +{ + public Task ExecuteAsync() + { + Console.WriteLine("Hello from the CLI!"); + return Task.FromResult(CommandResult.Success); + } +} +``` + +Run it: + +```bash +dotnet run -- greet +# Output: Hello from the CLI! + +dotnet run -- --help +# Output: Lists all available commands +``` + +The hosting framework automatically discovers commands in the entry assembly by scanning for classes decorated with `[CliCommand]`. + +## Commands with options + +Commands can accept strongly-typed options that are automatically parsed from arguments: + +```csharp +using CreativeCoders.SysConsole.Cli.Parsing; + +public class DeployOptions +{ + [OptionValue(0, HelpText = "The target environment")] + public string? Environment { get; set; } + + [OptionParameter('t', "tag", HelpText = "Docker image tag", DefaultValue = "latest")] + public string? Tag { get; set; } + + [OptionParameter('d', "dry-run", HelpText = "Preview without applying changes")] + public bool DryRun { get; set; } +} +``` + +```csharp +[CliCommand(["deploy"], Name = "deploy", Description = "Deploy the application")] +public class DeployCommand : ICliCommand +{ + public Task ExecuteAsync(DeployOptions options) + { + Console.WriteLine($"Deploying to {options.Environment} with tag {options.Tag}"); + + if (options.DryRun) + { + Console.WriteLine("(dry run — no changes applied)"); + } + + return Task.FromResult(CommandResult.Success); + } +} +``` + +```bash +dotnet run -- deploy staging --tag v2.1.0 --dry-run +``` + +## Command groups + +Organize related commands under a common group using multi-segment command paths: + +```csharp +using CreativeCoders.Cli.Core; + +// Register the group (assembly-level attribute) +[assembly: CliCommandGroup(["project"], "Project management commands")] +``` + +```csharp +[CliCommand(["project", "init"], Name = "init", Description = "Initialize a new project")] +public class ProjectInitCommand : ICliCommand +{ + public Task ExecuteAsync() + { + Console.WriteLine("Project initialized."); + return Task.FromResult(CommandResult.Success); + } +} + +[CliCommand(["project", "build"], Name = "build", Description = "Build the project")] +public class ProjectBuildCommand : ICliCommand +{ + public Task ExecuteAsync() + { + Console.WriteLine("Project built."); + return Task.FromResult(CommandResult.Success); + } +} +``` + +```bash +dotnet run -- project init +dotnet run -- project build +dotnet run -- help project # Lists commands in the "project" group +``` + +## Alternative commands + +Commands can define aliases via `AlternativeCommands`: + +```csharp +[CliCommand(["project", "init"], AlternativeCommands = ["init"])] +public class ProjectInitCommand : ICliCommand { /* ... */ } +``` + +Now both `project init` and `init` invoke the same command. + +## Dependency injection + +Commands are resolved through the DI container. Register services on the builder: + +```csharp +var host = CliHostBuilder.Create() + .EnableHelp(HelpCommandKind.CommandOrArgument) + .ConfigureServices(services => + { + services.AddSingleton(); + }) + .Build(); + +await host.RunMainAsync(args); +``` + +Inject dependencies via primary constructors: + +```csharp +[CliCommand(["deploy"], Name = "deploy", Description = "Deploy the application")] +public class DeployCommand(IDeployService deployService) : ICliCommand +{ + public async Task ExecuteAsync() + { + await deployService.DeployAsync(); + return CommandResult.Success; + } +} +``` + +## Options validation + +Enable validation on the host builder and implement `IOptionsValidation` on your options class: + +```csharp +var host = CliHostBuilder.Create() + .UseValidation() + .EnableHelp(HelpCommandKind.CommandOrArgument) + .Build(); +``` + +```csharp +public class DeployOptions : IOptionsValidation +{ + [OptionValue(0, HelpText = "The target environment")] + public string? Environment { get; set; } + + public Task ValidateAsync() + { + if (string.IsNullOrWhiteSpace(Environment)) + { + return Task.FromResult( + OptionsValidationResult.Invalid(["Environment is required"])); + } + + return Task.FromResult(OptionsValidationResult.Valid()); + } +} +``` + +When validation fails, the error messages are printed to the console automatically. + +## Pre- and post-processors + +Add logic that runs before or after every command — useful for headers, footers, or telemetry: + +```csharp +var host = CliHostBuilder.Create() + .EnableHelp(HelpCommandKind.CommandOrArgument) + .PrintHeaderMarkup(["[bold green]My CLI Tool v1.0[/]"]) + .PrintFooterText(["Done."]) + .Build(); +``` + +For custom logic, implement `ICliPreProcessor` or `ICliPostProcessor`: + +```csharp +public class TimingPreProcessor : ICliPreProcessor +{ + public CliProcessorExecutionCondition ExecutionCondition + => CliProcessorExecutionCondition.OnlyOnCommand; + + public Task ExecuteAsync(string[] args) + { + Console.WriteLine($"Started at {DateTime.Now:T}"); + return Task.CompletedTask; + } +} +``` + +Register it on the builder: + +```csharp +builder.RegisterPreProcessor(); +``` + +The `CliProcessorExecutionCondition` controls when the processor runs: + +| Value | Runs when | +|---|---| +| `Always` | Every CLI invocation | +| `OnlyOnCommand` | A command is executed | +| `OnlyOnHelp` | Help output is displayed | + +## Help system + +Enable help with one or more `HelpCommandKind` values: + +```csharp +builder.EnableHelp(HelpCommandKind.CommandOrArgument); +``` + +| Kind | Trigger | +|---|---| +| `Command` | `help` as the first argument | +| `Argument` | `--help` anywhere in args | +| `EmptyArgs` | No arguments provided | +| `CommandOrArgument` | Either `help` or `--help` | + +Help output is auto-generated from command attributes and option annotations. + +## Configuration + +Add configuration sources (JSON files, environment variables, etc.) via `UseConfiguration`: + +```csharp +builder.UseConfiguration(config => +{ + config.AddJsonFile("appsettings.json", optional: true); + config.AddEnvironmentVariables(); +}); +``` + +The resulting `IConfiguration` is available for injection in your commands. + +## Assembly scanning + +By default, the entry assembly is scanned for commands. To scan additional assemblies: + +```csharp +builder.ScanAssemblies(typeof(SomeCommandInAnotherAssembly).Assembly); +``` + +To disable entry assembly scanning (e.g., when all commands live in external libraries): + +```csharp +builder.SkipScanEntryAssembly(); +``` + +## Full example + +A complete sample application is available in [`samples/CliHostSampleApp`](../../samples/CliHostSampleApp). diff --git a/tests/CreativeCoders.Cli.Tests/Hosting/Commands/CliCommandStructureValidatorTests.cs b/tests/CreativeCoders.Cli.Tests/Hosting/Commands/CliCommandStructureValidatorTests.cs index 58ca8a9f..bf9c49cf 100644 --- a/tests/CreativeCoders.Cli.Tests/Hosting/Commands/CliCommandStructureValidatorTests.cs +++ b/tests/CreativeCoders.Cli.Tests/Hosting/Commands/CliCommandStructureValidatorTests.cs @@ -155,6 +155,38 @@ public void Validate_WithDuplicateAlternativeCommandPaths_ThrowsException() .ThrowExactly(); } + // + /// Ensures duplicate alternative command paths are treated as ambiguous. + /// + [Fact] + public void Validate_WithAlternativeCommandPaths_ThrowsNoException() + { + // Arrange + var commandStore = A.Fake(); + + A.CallTo(() => commandStore.GroupAttributes) + .Returns(Array.Empty()); + + var commands = new[] + { + CreateCommandInfo(["main", "command"], ["alias", "run"]), + CreateCommandInfo(["other", "command"], ["alias", "run1"]) + }; + + A.CallTo(() => commandStore.Commands) + .Returns(commands); + + var validator = new CliCommandStructureValidator(); + + // Act + var action = () => validator.Validate(commandStore); + + // Assert + action + .Should() + .NotThrow(); + } + private static CliCommandInfo CreateCommandInfo(string[] commands, string[]? alternativeCommands = null) { // Builds a command info instance mimicking CLI command definitions for validator inputs. From 88f98f915e9bd30d797dc97d9527cd02d7e891a0 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Sat, 11 Apr 2026 18:23:34 +0200 Subject: [PATCH 4/5] Add `README.md` for `CreativeCoders.CakeBuild` with comprehensive documentation - Introduced a detailed `README.md` explaining features, installation, setup, and usage for the `CreativeCoders.CakeBuild` library. - Included code examples for fluent builder usage, custom build contexts, task configuration, GitHub Actions integration, and tool installation. - Added task descriptions, settings interface documentation, and a sample project reference for better onboarding and adoption. --- .../CreativeCoders.CakeBuild/README.md | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 source/CakeBuild/CreativeCoders.CakeBuild/README.md diff --git a/source/CakeBuild/CreativeCoders.CakeBuild/README.md b/source/CakeBuild/CreativeCoders.CakeBuild/README.md new file mode 100644 index 00000000..ee8d0958 --- /dev/null +++ b/source/CakeBuild/CreativeCoders.CakeBuild/README.md @@ -0,0 +1,149 @@ +# CreativeCoders.CakeBuild + +A reusable build automation framework built on [Cake Frosting](https://cakebuild.net/docs/running-builds/runners/cake-frosting) for .NET projects. Provides a fluent builder API with pre-built CI/CD tasks — clean, build, test, pack, publish, create GitHub releases, and more — so you can set up a complete build pipeline with minimal code. + +## Features + +- 🏗️ **Fluent Builder API** — Configure your build pipeline with `CakeHostBuilder` in just a few lines +- 📦 **Pre-built Tasks** — Standard CI/CD tasks out of the box: Clean, Restore, Build, Test, Pack, Publish, NuGet Push, Code Coverage, GitHub Releases, Distribution Packages +- ⚙️ **Settings Interfaces** — Customize task behavior by implementing strongly-typed settings interfaces +- 🔍 **Auto-Discovery** — Automatically finds Git root, solution files, and test projects +- 🏷️ **GitVersion Integration** — Semantic versioning via GitVersion with static fallback +- 🐙 **GitHub Actions Support** — Log grouping and build server integration + +## Getting Started + +### Prerequisites + +- [.NET 10 SDK](https://dotnet.microsoft.com/download) or later + +### Setup + +Create a new console application and reference the `CreativeCoders.CakeBuild` package: + +```xml + + + Exe + net10.0 + + + + +``` + +### Minimal Example + +```csharp +using CreativeCoders.CakeBuild; + +CakeHostBuilder.Create() + .UseBuildContext() + .AddDefaultTasks() + .AddBuildServerIntegration() + .InstallTools( + new DotNetToolInstallation("GitVersion.Tool", "6.5.1"), + new DotNetToolInstallation("dotnet-reportgenerator-globaltool", "5.5.1")) + .Build() + .Run(args); +``` + +## Usage + +### Custom Build Context + +Extend `CakeBuildContext` and implement the settings interfaces for the tasks you want to configure: + +```csharp +public class MyBuildContext(ICakeContext context) : CakeBuildContext(context), + IDefaultTaskSettings, + ICreateDistPackagesTaskSettings +{ + public string Copyright => $"{DateTime.Now.Year} My Company"; + + public string PackageProjectUrl => "https://github.com/my-org/my-repo"; + + public string PackageLicenseExpression => PackageLicenseExpressions.Apache20; + + public string NuGetFeedUrl => "https://api.nuget.org/v3/index.json"; + + public IEnumerable DistPackages => + [ + new("my-app-linux-x64", "artifacts/publish/my-app/linux-x64", DistPackageFormat.TarGz), + new("my-app-win-x64", "artifacts/publish/my-app/win-x64", DistPackageFormat.Zip) + ]; +} +``` + +### Available Tasks + +All default tasks are registered via `AddDefaultTasks()` and execute in dependency order: + +| Task | Description | Depends On | +|------|-------------|------------| +| **Clean** | Removes `bin/`, `obj/`, and artifact directories | — | +| **Restore** | Restores NuGet packages | Clean | +| **Build** | Builds the solution with version info from GitVersion | Restore | +| **Test** | Runs tests with optional code coverage collection | Build | +| **CodeCoverage** | Generates coverage reports via ReportGenerator | Test | +| **Pack** | Creates NuGet packages with metadata | Build | +| **NuGetPush** | Pushes packages to a NuGet feed | Pack | +| **Publish** | Publishes applications to output directories | Build | +| **CreateDistPackages** | Creates `.tar.gz` / `.zip` distribution archives | Publish | +| **CreateGitHubRelease** | Creates a GitHub release with assets via Octokit | — | + +### Settings Interfaces + +Each task reads its configuration from a settings interface. Implement only the ones you need: + +| Interface | Configures | +|-----------|------------| +| `ICleanTaskSettings` | Directories to clean | +| `ITestTaskSettings` | Test projects, coverage options | +| `ICodeCoverageTaskSettings` | Report types and file patterns | +| `IPackTaskSettings` | Package output, metadata (URL, license, copyright) | +| `INuGetPushTaskSettings` | Feed URL, API key, skip flag | +| `IPublishTaskSettings` | Per-project publish configuration (runtime, self-contained) | +| `ICreateDistPackagesTaskSettings` | Distribution package definitions and output path | +| `ICreateGitHubReleaseTaskSettings` | Release metadata, assets, GitHub token | + +> [!TIP] +> Implement `IDefaultTaskSettings` to get all standard settings interfaces in one go. + +### Tool Installation + +Register external tools via the builder: + +```csharp +CakeHostBuilder.Create() + .InstallTools( + new DotNetToolInstallation("GitVersion.Tool", "6.5.1"), + new NuGetToolInstallation("ReportGenerator", "5.5.1")) + // ... +``` + +### GitHub Actions Integration + +Enable log grouping for GitHub Actions: + +```csharp +CakeHostBuilder.Create() + .AddBuildServerIntegration() + // ... +``` + +This registers task setup/teardown hooks that create collapsible log groups in GitHub Actions. + +### Generic Task Templates + +For advanced scenarios, use the generic task templates (`BuildTask`, `TestTask`, etc.) with a custom context type instead of the default `CakeBuildContext`: + +```csharp +[TaskName("Build")] +[IsDependentOn(typeof(RestoreTask))] +public class MyBuildTask : BuildTask { } +``` + +## Sample + +See [`samples/CakeBuildSample`](../../../samples/CakeBuildSample) for a complete working example that demonstrates the full pipeline setup with custom context, publishing, and distribution package creation. From 01e34c3bff150d13b18f500d78da8e3c846f5201 Mon Sep 17 00:00:00 2001 From: darthsharp <48331467+darthsharp@users.noreply.github.com> Date: Sat, 11 Apr 2026 18:26:27 +0200 Subject: [PATCH 5/5] Update `README.md`: Replace `ReportGenerator` tool with `dotnet-reportgenerator-globaltool` --- source/CakeBuild/CreativeCoders.CakeBuild/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/CakeBuild/CreativeCoders.CakeBuild/README.md b/source/CakeBuild/CreativeCoders.CakeBuild/README.md index ee8d0958..8baf9aea 100644 --- a/source/CakeBuild/CreativeCoders.CakeBuild/README.md +++ b/source/CakeBuild/CreativeCoders.CakeBuild/README.md @@ -118,7 +118,7 @@ Register external tools via the builder: CakeHostBuilder.Create() .InstallTools( new DotNetToolInstallation("GitVersion.Tool", "6.5.1"), - new NuGetToolInstallation("ReportGenerator", "5.5.1")) + new DotNetToolInstallation("dotnet-reportgenerator-globaltool", "5.5.1")) // ... ```