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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Tools/CLI/FSH.CLI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<PackageReference Include="Spectre.Console" />
<PackageReference Include="Spectre.Console.Cli" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>

<ItemGroup>
Expand Down
42 changes: 42 additions & 0 deletions src/Tools/CLI/Scaffolding/ITemplateCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace FSH.CLI.Scaffolding;

/// <summary>
/// Caching layer for templates
/// </summary>
internal interface ITemplateCache
{
/// <summary>
/// Gets a cached template by key
/// </summary>
string? GetTemplate(string key);

/// <summary>
/// Stores a template in the cache
/// </summary>
void SetTemplate(string key, string template);

/// <summary>
/// Checks if a template is cached
/// </summary>
bool ContainsTemplate(string key);

/// <summary>
/// Removes a template from the cache
/// </summary>
void RemoveTemplate(string key);

/// <summary>
/// Clears all cached templates
/// </summary>
void ClearCache();

/// <summary>
/// Gets cache statistics
/// </summary>
CacheStatistics GetStatistics();
}

/// <summary>
/// Cache performance statistics
/// </summary>
internal record CacheStatistics(int TotalEntries, int HitCount, int MissCount, double HitRatio);
24 changes: 24 additions & 0 deletions src/Tools/CLI/Scaffolding/ITemplateLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using FSH.CLI.Models;

namespace FSH.CLI.Scaffolding;

/// <summary>
/// Loads templates from various sources (embedded resources, disk, etc.)
/// </summary>
internal interface ITemplateLoader
{
/// <summary>
/// Gets the framework version for package references
/// </summary>
string GetFrameworkVersion();

/// <summary>
/// Gets a static template by name
/// </summary>
string GetStaticTemplate(string templateName);

/// <summary>
/// Checks if a template exists
/// </summary>
bool TemplateExists(string templateName);
}
36 changes: 36 additions & 0 deletions src/Tools/CLI/Scaffolding/ITemplateParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using FSH.CLI.Models;

namespace FSH.CLI.Scaffolding;

/// <summary>
/// Parses template syntax and extracts variables
/// </summary>
internal interface ITemplateParser
{
/// <summary>
/// Extracts variables from a template string
/// </summary>
IEnumerable<string> ExtractVariables(string template);

/// <summary>
/// Validates template syntax
/// </summary>
bool IsValidTemplate(string template);

/// <summary>
/// Normalizes project names for different contexts (lowercase, safe characters, etc.)
/// </summary>
string NormalizeProjectName(string projectName, NameContext context);
}

/// <summary>
/// Context for project name normalization
/// </summary>
internal enum NameContext
{
Default,
LowerCase,
SafeIdentifier,
DockerImage,
DatabaseName
}
54 changes: 54 additions & 0 deletions src/Tools/CLI/Scaffolding/ITemplateRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using FSH.CLI.Models;

namespace FSH.CLI.Scaffolding;

/// <summary>
/// Renders templates with variable substitution
/// </summary>
internal interface ITemplateRenderer
{
// Solution and Project Templates
string RenderSolution(ProjectOptions options);
string RenderApiCsproj(ProjectOptions options);
string RenderApiProgram(ProjectOptions options);
string RenderMigrationsCsproj(ProjectOptions options);
string RenderBlazorCsproj();
string RenderBlazorProgram(ProjectOptions options);
string RenderAppHostCsproj(ProjectOptions options);
string RenderAppHostProgram(ProjectOptions options);

// Configuration Templates
string RenderAppSettings(ProjectOptions options);
string RenderAppSettingsDevelopment();
string RenderApiLaunchSettings(ProjectOptions options);
string RenderAppHostLaunchSettings(ProjectOptions options);

// Blazor Templates
string RenderBlazorApp();
string RenderBlazorImports(ProjectOptions options);
string RenderBlazorIndexPage(ProjectOptions options);
string RenderBlazorMainLayout(ProjectOptions options);

// Infrastructure Templates
string RenderDockerfile(ProjectOptions options);
string RenderDockerCompose(ProjectOptions options);
string RenderDockerComposeOverride();
string RenderTerraformMain(ProjectOptions options);
string RenderTerraformVariables(ProjectOptions options);
string RenderTerraformOutputs(ProjectOptions options);
string RenderGitHubActionsCI(ProjectOptions options);

// Module Templates
string RenderCatalogModule(ProjectOptions options);
string RenderCatalogModuleCsproj(ProjectOptions options);
string RenderCatalogContractsCsproj();
string RenderGetProductsEndpoint(ProjectOptions options);

// Static Content Templates
string RenderReadme(ProjectOptions options);
string RenderGitignore();
string RenderDirectoryBuildProps(ProjectOptions options);
string RenderDirectoryPackagesProps(ProjectOptions options);
string RenderEditorConfig();
string RenderGlobalJson();
}
41 changes: 41 additions & 0 deletions src/Tools/CLI/Scaffolding/ITemplateValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using FSH.CLI.Models;

namespace FSH.CLI.Scaffolding;

/// <summary>
/// Validates template structure and configuration
/// </summary>
internal interface ITemplateValidator
{
/// <summary>
/// Validates project options for template generation
/// </summary>
ValidationResult ValidateProjectOptions(ProjectOptions options);

/// <summary>
/// Validates that required templates are available
/// </summary>
ValidationResult ValidateTemplateAvailability(ProjectOptions options);

/// <summary>
/// Validates generated template content
/// </summary>
ValidationResult ValidateGeneratedContent(string content, string templateType);

/// <summary>
/// Validates project structure compatibility
/// </summary>
ValidationResult ValidateProjectStructure(ProjectOptions options);
}

/// <summary>
/// Result of a validation operation
/// </summary>
internal record ValidationResult(bool IsValid, IEnumerable<string> Errors, IEnumerable<string> Warnings)
{
public static ValidationResult Success() => new(true, Enumerable.Empty<string>(), Enumerable.Empty<string>());

public static ValidationResult Failure(params string[] errors) => new(false, errors, Enumerable.Empty<string>());

public static ValidationResult Warning(params string[] warnings) => new(true, Enumerable.Empty<string>(), warnings);
}
91 changes: 91 additions & 0 deletions src/Tools/CLI/Scaffolding/REFACTORING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# TemplateEngine Refactoring

This document outlines the refactoring of the TemplateEngine.cs god class into focused, single-responsibility components.

## Overview

The original `TemplateEngine.cs` was a 1645-line god class that handled everything from template loading to rendering and validation. This refactoring splits it into focused services while maintaining backward compatibility.

## New Architecture

### 1. ITemplateLoader / TemplateLoader
**Responsibility:** Load templates from embedded resources and static sources
- `GetFrameworkVersion()` - Gets the current framework version
- `GetStaticTemplate(name)` - Gets static template content by name
- `TemplateExists(name)` - Checks if a template exists

### 2. ITemplateParser / TemplateParser
**Responsibility:** Parse template syntax and normalize project names
- `ExtractVariables(template)` - Extracts interpolation variables
- `IsValidTemplate(template)` - Validates template syntax
- `NormalizeProjectName(name, context)` - Normalizes names for different contexts (Docker, database, etc.)

### 3. ITemplateRenderer / TemplateRenderer
**Responsibility:** Render templates with variable substitution
- All the `Render*()` methods that generate specific templates
- Handles the actual template interpolation logic
- Uses other services for loading, parsing, and validation

### 4. ITemplateValidator / TemplateValidator
**Responsibility:** Validate template structure and project configuration
- `ValidateProjectOptions()` - Validates input parameters
- `ValidateTemplateAvailability()` - Ensures required templates exist
- `ValidateGeneratedContent()` - Validates output quality
- `ValidateProjectStructure()` - Checks for logical issues

### 5. ITemplateCache / TemplateCache
**Responsibility:** Cache frequently used templates
- In-memory caching with statistics
- Thread-safe operations
- Cache hit/miss metrics

### 6. TemplateServices
**Responsibility:** Dependency injection container
- Factory pattern for creating services
- Manages service lifetimes
- Provides easy access to all services

## Backward Compatibility

The refactored `TemplateEngine` class maintains the exact same public API as before. All existing code calling `TemplateEngine.GenerateXxx()` methods will continue to work without changes.

## Benefits

1. **Single Responsibility Principle** - Each class has one clear purpose
2. **Testability** - Smaller, focused classes are easier to unit test
3. **Maintainability** - Changes are isolated to specific areas
4. **Extensibility** - New template types or sources are easier to add
5. **Performance** - Template caching reduces repeated work
6. **Validation** - Built-in validation catches issues early

## Files Changed

- `TemplateEngine.cs` - Refactored to delegate to services (maintains API)
- `ITemplateLoader.cs` + `TemplateLoader.cs` - Template loading
- `ITemplateParser.cs` + `TemplateParser.cs` - Template parsing
- `ITemplateRenderer.cs` + `TemplateRenderer.cs` - Template rendering
- `ITemplateValidator.cs` + `TemplateValidator.cs` - Validation logic
- `ITemplateCache.cs` + `TemplateCache.cs` - Caching layer
- `TemplateServices.cs` - DI container
- `TemplateEngineTests.cs` - Basic validation tests
- `FSH.CLI.csproj` - Added Microsoft.Extensions.DependencyInjection

## Testing

Run `TemplateEngineTests.RunValidationTests()` to verify the refactoring works correctly.

## Migration Notes

- No breaking changes - existing code continues to work
- The original `TemplateEngine.cs` is backed up as `TemplateEngine.cs.backup`
- All template generation logic has been moved but preserved
- Validation now provides better error messages and warnings

## Future Enhancements

1. **Template Hot Reloading** - Watch template files and reload automatically
2. **Custom Template Sources** - Load from databases, HTTP, etc.
3. **Template Composition** - Combine multiple templates
4. **Async Operations** - For I/O heavy template operations
5. **Template Versioning** - Support multiple template versions
6. **Plugin Architecture** - Allow external template providers
64 changes: 64 additions & 0 deletions src/Tools/CLI/Scaffolding/TemplateCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Concurrent;

namespace FSH.CLI.Scaffolding;

/// <summary>
/// In-memory cache for templates
/// </summary>
internal sealed class TemplateCache : ITemplateCache
{
private readonly ConcurrentDictionary<string, string> _cache = new();
private int _hitCount;
private int _missCount;

public string? GetTemplate(string key)
{
ArgumentException.ThrowIfNullOrEmpty(key);

if (_cache.TryGetValue(key, out var template))
{
Interlocked.Increment(ref _hitCount);
return template;
}

Interlocked.Increment(ref _missCount);
return null;
}

public void SetTemplate(string key, string template)
{
ArgumentException.ThrowIfNullOrEmpty(key);
ArgumentNullException.ThrowIfNull(template);

_cache[key] = template;
}

public bool ContainsTemplate(string key)
{
ArgumentException.ThrowIfNullOrEmpty(key);

return _cache.ContainsKey(key);
}

public void RemoveTemplate(string key)
{
ArgumentException.ThrowIfNullOrEmpty(key);

_cache.TryRemove(key, out _);
}

public void ClearCache()
{
_cache.Clear();
Interlocked.Exchange(ref _hitCount, 0);
Interlocked.Exchange(ref _missCount, 0);
}

public CacheStatistics GetStatistics()
{
var totalRequests = _hitCount + _missCount;
var hitRatio = totalRequests > 0 ? (double)_hitCount / totalRequests : 0.0;

return new CacheStatistics(_cache.Count, _hitCount, _missCount, hitRatio);
}
}
Loading
Loading