Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.2" Condition="'$(TargetFramework)' == 'net10.0'" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.12" Condition="'$(TargetFramework)' != 'net10.0'" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(AspNetCoreVersion10)" />
<PackageVersion Include="Microsoft.Extensions.Localization" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
Expand Down
4 changes: 2 additions & 2 deletions Havit.Blazor.Documentation.Mcp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Azure.Monitor.OpenTelemetry.AspNetCore;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
using Havit.Blazor.Documentation.Mcp.Services;
using Havit.Blazor.Documentation.Mcp.Tools;
using Havit.Blazor.Documentation.Services;
using OpenTelemetry;
Expand All @@ -19,7 +18,8 @@
builder.Services.AddSingleton<IDocXmlProvider, DocXmlProvider>();
builder.Services.AddSingleton<IApiDocModelBuilder, ApiDocModelBuilder>();
builder.Services.AddSingleton<IApiDocModelProvider, ApiDocModelProvider>();
builder.Services.AddSingleton<McpDocMarkdownRenderer>();
builder.Services.AddSingleton<IDocMarkdownRenderer, DocMarkdownRenderer>();
builder.Services.AddSingleton<IComponentDemosProvider, ComponentDemosProvider>();
builder.Services.AddSingleton<IDocumentationCatalogService, DocumentationCatalogService>();

builder.Services
Expand Down
55 changes: 6 additions & 49 deletions Havit.Blazor.Documentation.Mcp/Tools/GetComponentDocsTool.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
using Havit.Blazor.Documentation.Mcp.Services;
using Havit.Blazor.Documentation.Model;
using Havit.Blazor.Documentation.Services;
using ModelContextProtocol.Server;
Expand All @@ -14,15 +12,15 @@ namespace Havit.Blazor.Documentation.Mcp.Tools;
/// </summary>
internal class GetComponentDocsTool
{
private static readonly Assembly s_documentationAssembly = typeof(DocumentationCatalogService).Assembly;

private readonly IApiDocModelProvider _modelProvider;
private readonly McpDocMarkdownRenderer _renderer;
private readonly IDocMarkdownRenderer _renderer;
private readonly IComponentDemosProvider _componentDemosProvider;

public GetComponentDocsTool(IApiDocModelProvider modelProvider, McpDocMarkdownRenderer renderer)
public GetComponentDocsTool(IApiDocModelProvider modelProvider, IDocMarkdownRenderer renderer, IComponentDemosProvider componentDemosProvider)
{
_modelProvider = modelProvider;
_renderer = renderer;
_componentDemosProvider = componentDemosProvider;
}

/// <summary>
Expand All @@ -44,7 +42,7 @@ public string GetComponentDocs(
}

ApiDocModel model = _modelProvider.GetApiDocModel(type);
IReadOnlyList<string> sampleNames = GetSampleNames(componentName);
IReadOnlyList<string> sampleNames = _componentDemosProvider.GetComponentDemoFileNames(componentName);
string markdown = _renderer.RenderComponentDoc(model, sampleNames);

activity?.SetTag("mcp.tool.result", "success");
Expand All @@ -53,45 +51,4 @@ public string GetComponentDocs(

return markdown;
}

private static IReadOnlyList<string> GetSampleNames(string componentName)
{
// Normalize component name by stripping generic type arguments (e.g., "HxGrid<TItem>" -> "HxGrid")
// This matches the behavior of ApiTypeHelper.GetType()
int openingBracePosition = componentName.IndexOf("<");
if (openingBracePosition > 0)
{
componentName = componentName[..openingBracePosition];
}

return s_documentationAssembly.GetManifestResourceNames()
.Where(r => r.EndsWith(".razor", StringComparison.Ordinal)
&& r.Contains("Demo", StringComparison.Ordinal)
&& ExtractFileName(r).StartsWith(componentName + "_", StringComparison.OrdinalIgnoreCase))
.OrderBy(r => r, StringComparer.Ordinal)
.Select(r => ExtractFileName(r))
.ToList();
}

/// <summary>
/// Extracts the file name from an embedded resource name.
/// E.g. "Havit.Blazor.Documentation.Pages.Components.HxAccordionDoc.HxAccordion_PlainDemo.razor" → "HxAccordion_PlainDemo.razor".
/// </summary>
private static string ExtractFileName(string resourceName)
{
int extensionIndex = resourceName.LastIndexOf(".razor", StringComparison.Ordinal);
if (extensionIndex < 0)
{
return resourceName;
}

string withoutExtension = resourceName[..extensionIndex];
int lastDot = withoutExtension.LastIndexOf('.');
if (lastDot < 0)
{
return resourceName;
}

return withoutExtension[(lastDot + 1)..] + ".razor";
}
}
50 changes: 12 additions & 38 deletions Havit.Blazor.Documentation.Mcp/Tools/GetComponentSamplesTool.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
using Havit.Blazor.Documentation.Services;
Expand All @@ -13,7 +12,12 @@ namespace Havit.Blazor.Documentation.Mcp.Tools;
/// </summary>
internal class GetComponentSamplesTool
{
private static readonly Assembly s_documentationAssembly = typeof(DocumentationCatalogService).Assembly;
private readonly IComponentDemosProvider _componentDemosProvider;

public GetComponentSamplesTool(IComponentDemosProvider componentDemosProvider)
{
_componentDemosProvider = componentDemosProvider;
}

/// <summary>
/// Returns all demo samples for a given HAVIT Blazor component in Markdown format.
Expand All @@ -26,33 +30,25 @@ public string GetComponentSamples(
using Activity activity = McpToolActivitySource.Source.StartActivity("get_component_samples");
activity?.SetTag("mcp.tool.parameter.componentName", componentName);

string[] resourceNames = s_documentationAssembly.GetManifestResourceNames()
.Where(r => r.EndsWith(".razor", StringComparison.Ordinal)
&& r.Contains("Demo", StringComparison.Ordinal)
&& ExtractFileName(r).StartsWith(componentName + "_", StringComparison.OrdinalIgnoreCase))
.OrderBy(r => r, StringComparer.Ordinal)
.ToArray();
IReadOnlyList<string> resourceNames = _componentDemosProvider.GetComponentDemoResourceNames(componentName);

if (resourceNames.Length == 0)
if (resourceNames.Count == 0)
{
activity?.SetTag("mcp.tool.result", "not_found");
return $"No demo samples found for component '{componentName}'.";
}

activity?.SetTag("mcp.tool.result", "success");
activity?.SetTag("mcp.tool.sampleCount", resourceNames.Length);
activity?.SetTag("mcp.tool.sampleCount", resourceNames.Count);

StringBuilder sb = new StringBuilder();
sb.AppendLine($"# {componentName} — Demo Samples");
sb.AppendLine();

foreach (string resourceName in resourceNames)
{
string fileName = ExtractFileName(resourceName);

using Stream stream = s_documentationAssembly.GetManifestResourceStream(resourceName);
using StreamReader reader = new StreamReader(stream);
string content = reader.ReadToEnd();
string fileName = ComponentDemosProvider.ExtractFileName(resourceName);
string content = _componentDemosProvider.GetDemoContentByResourceName(resourceName);

sb.AppendLine($"## {fileName}");
sb.AppendLine();
Expand All @@ -64,26 +60,4 @@ public string GetComponentSamples(

return sb.ToString();
}

/// <summary>
/// Extracts the file name from an embedded resource name.
/// E.g. "Havit.Blazor.Documentation.Pages.Components.HxAccordionDoc.HxAccordion_PlainDemo.razor" → "HxAccordion_PlainDemo.razor".
/// </summary>
private static string ExtractFileName(string resourceName)
{
int extensionIndex = resourceName.LastIndexOf(".razor", StringComparison.Ordinal);
if (extensionIndex < 0)
{
return resourceName;
}

string withoutExtension = resourceName[..extensionIndex];
int lastDot = withoutExtension.LastIndexOf('.');
if (lastDot < 0)
{
return resourceName;
}

return withoutExtension[(lastDot + 1)..] + ".razor";
}
}
5 changes: 2 additions & 3 deletions Havit.Blazor.Documentation.Mcp/Tools/GetTypeDocTool.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.ComponentModel;
using System.Diagnostics;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
using Havit.Blazor.Documentation.Mcp.Services;
using Havit.Blazor.Documentation.Model;
using Havit.Blazor.Documentation.Services;
using ModelContextProtocol.Server;
Expand All @@ -15,9 +14,9 @@ namespace Havit.Blazor.Documentation.Mcp.Tools;
internal class GetTypeDocTool
{
private readonly IApiDocModelProvider _modelProvider;
private readonly McpDocMarkdownRenderer _renderer;
private readonly IDocMarkdownRenderer _renderer;

public GetTypeDocTool(IApiDocModelProvider modelProvider, McpDocMarkdownRenderer renderer)
public GetTypeDocTool(IApiDocModelProvider modelProvider, IDocMarkdownRenderer renderer)
{
_modelProvider = modelProvider;
_renderer = renderer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Havit.Blazor.Documentation.RepoDumpGenerator</AssemblyName>
<RootNamespace>Havit.Blazor.Documentation.RepoDumpGenerator</RootNamespace>
<RollForward>Major</RollForward>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Havit.Blazor.Components.Web.Bootstrap\Havit.Blazor.Components.Web.Bootstrap.csproj" />
<ProjectReference Include="..\Havit.Blazor.Components.Web\Havit.Blazor.Components.Web.csproj" />
<ProjectReference Include="..\Havit.Blazor.Documentation\Havit.Blazor.Documentation.csproj" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions Havit.Blazor.Documentation.RepoDumpGenerator/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Havit.Blazor.Documentation.RepoDumpGenerator.Services;
using Havit.Blazor.Documentation.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IDocXmlProvider, DocXmlProvider>();
builder.Services.AddSingleton<IApiDocModelBuilder, ApiDocModelBuilder>();
builder.Services.AddSingleton<IApiDocModelProvider, ApiDocModelProvider>();
builder.Services.AddSingleton<IDocMarkdownRenderer, DocMarkdownRenderer>();
builder.Services.AddSingleton<IComponentDemosProvider, ComponentDemosProvider>();
builder.Services.AddSingleton<IDocumentationCatalogService, DocumentationCatalogService>();
builder.Services.AddSingleton<DocDumpService>();

using IHost host = builder.Build();

string outputRoot = Path.Combine(FindRepoRoot(AppContext.BaseDirectory), "docs", "generated");

DocDumpService dumpService = host.Services.GetRequiredService<DocDumpService>();
dumpService.Run(outputRoot);

// --- Helper methods ---

static string FindRepoRoot(string startDir)
{
string dir = startDir;
while (dir is not null)
{
if (File.Exists(Path.Combine(dir, "Havit.Blazor.sln")))
{
return dir;
}
dir = Path.GetDirectoryName(dir);
}
throw new InvalidOperationException("Could not find repository root (Havit.Blazor.sln).");
}
Loading