diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/ArgumentsHelper.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/ArgumentsHelper.cs new file mode 100644 index 00000000..11f58288 --- /dev/null +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/ArgumentsHelper.cs @@ -0,0 +1,34 @@ +// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone + +using BootstrapBlazorLLMsDocsGenerator; +using System.CommandLine; + +namespace BootstrapBlazor.LLMsDocsGenerator; + +internal static class ArgumentsHelper +{ + public static ParseResult Parse(string[] args) + { + var rootFolderOption = new Option("--root") { Description = "Set the root folder of project" }; + + var rootCommand = new RootCommand("BootstrapBlazor LLMs Documentation Generator") + { + rootFolderOption + }; + + rootCommand.SetAction(async result => + { + var rootFolder = result.GetValue(rootFolderOption); + if (string.IsNullOrEmpty(rootFolder)) + { + return; + } + + await DocsGenerator.GenerateAllAsync(rootFolder); + }); + + return rootCommand.Parse(args); + } +} diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/BootstrapBlazor.LLMsDocsGenerator.csproj b/tools/BootstrapBlazor.LLMsDocsGenerator/BootstrapBlazor.LLMsDocsGenerator.csproj index 61893ece..c5f84f95 100644 --- a/tools/BootstrapBlazor.LLMsDocsGenerator/BootstrapBlazor.LLMsDocsGenerator.csproj +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/BootstrapBlazor.LLMsDocsGenerator.csproj @@ -1,11 +1,12 @@  - 10.0.0 + 10.0.1 Exe net10.0 enable enable + false diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentAnalyzer.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentAnalyzer.cs index a157d6a1..c171d47a 100644 --- a/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentAnalyzer.cs +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentAnalyzer.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Text.RegularExpressions; -namespace LlmsDocsGenerator; +namespace BootstrapBlazorLLMsDocsGenerator; /// /// Analyzes Blazor component source files using Roslyn diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentInfo.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentInfo.cs index 4c7ac9cd..d648b4f8 100644 --- a/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentInfo.cs +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/ComponentInfo.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone -namespace LlmsDocsGenerator; +namespace BootstrapBlazorLLMsDocsGenerator; /// /// Represents information about a Blazor component diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/DocsGenerator.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/DocsGenerator.cs index f5141654..8e6ea017 100644 --- a/tools/BootstrapBlazor.LLMsDocsGenerator/DocsGenerator.cs +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/DocsGenerator.cs @@ -3,241 +3,56 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone -namespace LlmsDocsGenerator; +namespace BootstrapBlazorLLMsDocsGenerator; -/// -/// Main documentation generator class -/// -public class DocsGenerator +internal static class DocsGenerator { - private readonly string _outputPath; - private readonly string _componentsOutputPath; - private readonly string _sourcePath; - private readonly ComponentAnalyzer _analyzer; - private readonly MarkdownBuilder _markdownBuilder; - private readonly bool _debug; - - public DocsGenerator(string? rootFolder, bool debug) - { - _debug = debug; - - // Find the source directory (relative to tool location or current directory) - var root = FindSourcePath(rootFolder); - - _sourcePath = Path.Combine(root, "src", "BootstrapBlazor"); - _outputPath = Path.Combine(root, "src", "BootstrapBlazor.Server", "wwwroot", "llms"); - _componentsOutputPath = Path.Combine(_outputPath, "components"); - _analyzer = new ComponentAnalyzer(_sourcePath); - _markdownBuilder = new MarkdownBuilder(); - } - - private string FindSourcePath(string? rootFolder) - { - // Try to find src/BootstrapBlazor from current directory or parent directories - var current = rootFolder ?? AppContext.BaseDirectory; - Logger($"Root path: {current}"); - - while (!string.IsNullOrEmpty(current)) - { - var parent = Directory.GetParent(current); - if (parent == null) - { - break; - } - if (parent.Name.Equals("BootstrapBlazor", StringComparison.OrdinalIgnoreCase)) - { - return parent.FullName; - } - current = parent.FullName; - } - - throw new DirectoryNotFoundException("Could not find src directory. Please run this tool from the BootstrapBlazor repository root."); - } - /// /// Generate all documentation files /// - public async Task GenerateAllAsync() + public static async Task GenerateAllAsync(string rootFolder) { + var _sourcePath = Path.Combine(rootFolder, "..", "BootstrapBlazor"); + var _outputPath = Path.Combine(rootFolder, "bin", "Release", "net10.0", "publish", "wwwroot", "llms"); + var _componentsOutputPath = Path.Combine(_outputPath, "components"); + Logger($"Source path: {_sourcePath}"); Logger($"Output path: {_outputPath}"); Logger($"Components path: {_componentsOutputPath}"); - // Ensure output directories exist - Directory.CreateDirectory(_outputPath); - Directory.CreateDirectory(_componentsOutputPath); - - // Analyze all components - Logger("Analyzing components..."); - var components = await _analyzer.AnalyzeAllComponentsAsync(); - Logger($"Found {components.Count} components"); - - // Generate index file - await GenerateIndexAsync(components); - - // Generate individual component documentation files - Logger("Generating individual component documentation..."); - foreach (var component in components) + if (!Directory.Exists(_sourcePath)) { - await GenerateComponentDocAsync(component); - } - - Logger("Documentation generation complete!"); - } - - /// - /// Generate only the index file - /// - public async Task GenerateIndexAsync() - { - var components = await _analyzer.AnalyzeAllComponentsAsync(); - await GenerateIndexAsync(components); - } - - private async Task GenerateIndexAsync(List components) - { - // Ensure output directory exists - Directory.CreateDirectory(_outputPath); - - var indexPath = Path.Combine(_outputPath, "llms.txt"); - var content = _markdownBuilder.BuildIndex(components); - await File.WriteAllTextAsync(indexPath, content); - Logger($"Generated: {indexPath}"); - } - - /// - /// Generate documentation for a specific component - /// - public async Task GenerateComponentAsync(string componentName) - { - var component = await _analyzer.AnalyzeComponentAsync(componentName); - if (component == null) - { - Logger($"Component not found: {componentName}"); return; } - // Ensure output directory exists + Directory.CreateDirectory(_outputPath); Directory.CreateDirectory(_componentsOutputPath); - await GenerateComponentDocAsync(component); - } - - private async Task GenerateComponentDocAsync(ComponentInfo component) - { - var content = _markdownBuilder.BuildComponentDoc(component); - var fileName = $"{component.Name}.txt"; - var filePath = Path.Combine(_componentsOutputPath, fileName); - await File.WriteAllTextAsync(filePath, content); - Logger($"Generated: {filePath}"); - } - - /// - /// Check if documentation is up-to-date - /// - public async Task CheckAsync() - { - Logger("Checking documentation freshness..."); + Logger("Analyzing components..."); + var _analyzer = new ComponentAnalyzer(_sourcePath); var components = await _analyzer.AnalyzeAllComponentsAsync(); + Logger($"Found {components.Count} components"); - // Check index file var indexPath = Path.Combine(_outputPath, "llms.txt"); - if (!File.Exists(indexPath)) - { - Logger("OUTDATED: llms.txt does not exist"); - return false; - } - - var indexLastWrite = File.GetLastWriteTimeUtc(indexPath); - - // compute the most recent component source timestamp: - var newestComponentWrite = components - .Select(c => c.LastModified) - .DefaultIfEmpty(indexLastWrite) - .Max(); - - if (indexLastWrite < newestComponentWrite) - { - Logger("Index file is stale relative to component sources. Please regenerate docs."); - return false; - } + var content = MarkdownBuilder.BuildIndexDoc(components); + await File.WriteAllTextAsync(indexPath, content); + Logger($"Generated: {indexPath}"); - // Check each component file + Logger("Generating individual component documentation..."); foreach (var component in components) { + content = MarkdownBuilder.BuildComponentDoc(component); var fileName = $"{component.Name}.txt"; var filePath = Path.Combine(_componentsOutputPath, fileName); - - if (!File.Exists(filePath)) - { - Logger($"OUTDATED: {fileName} does not exist"); - return false; - } - - // Check if source file is newer than the doc file - var docLastWrite = File.GetLastWriteTimeUtc(filePath); - if (component.LastModified > docLastWrite) - { - Logger($"OUTDATED: {component.Name} was modified after {fileName}"); - return false; - } + await File.WriteAllTextAsync(filePath, content); + Logger($"Generated: {filePath}"); } - - Logger("Documentation is up-to-date"); - return true; - } - - private static string GetComponentCategory(string componentName) - { - return componentName.ToLowerInvariant() switch - { - // Table family - var n when n.Contains("table") => "table", - - // Input family - var n when n.Contains("input") || n.Contains("textarea") || - n.Contains("password") || n == "otpinput" => "input", - - // Select family - var n when n.Contains("select") || n.Contains("dropdown") || - n.Contains("autocomplete") || n.Contains("cascader") || - n.Contains("transfer") || n.Contains("multiselect") => "select", - - // Button family - var n when n.Contains("button") || n == "gotop" || - n.Contains("popconfirm") => "button", - - // Dialog family - var n when n.Contains("dialog") || n.Contains("modal") || - n.Contains("drawer") || n.Contains("swal") || - n.Contains("toast") || n.Contains("message") => "dialog", - - // Navigation family - var n when n.Contains("menu") || n.Contains("tab") || - n.Contains("breadcrumb") || n.Contains("step") || - n.Contains("anchor") || n.Contains("nav") => "nav", - - // Card/Container family - var n when n.Contains("card") || n.Contains("collapse") || - n.Contains("groupbox") || n.Contains("panel") => "card", - - // TreeView - var n when n.Contains("tree") => "treeview", - - // Form - var n when n.Contains("validateform") || n.Contains("editorform") || - n.Contains("validator") => "form", - - _ => "other" - }; + Logger("Documentation generation complete!"); } - private void Logger(string message) + private static void Logger(string message) { - if (_debug) - { - Console.WriteLine(message); - } + Console.WriteLine(message); } } diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/MarkdownBuilder.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/MarkdownBuilder.cs index 97c8271f..6d787d12 100644 --- a/tools/BootstrapBlazor.LLMsDocsGenerator/MarkdownBuilder.cs +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/MarkdownBuilder.cs @@ -5,23 +5,18 @@ using System.Text; -namespace LlmsDocsGenerator; +namespace BootstrapBlazorLLMsDocsGenerator; /// /// Builds Markdown documentation for components /// -public class MarkdownBuilder +internal static class MarkdownBuilder { private const string GitHubBaseUrl = "https://github.com/dotnetcore/BootstrapBlazor/blob/main/"; - private readonly StringBuilder _sb = new(); - /// - /// Build the main llms.txt index file - /// - public string BuildIndex(List components) + public static string BuildIndexDoc(List components) { - _sb.Clear(); - + var _sb = new StringBuilder(); _sb.AppendLine("# BootstrapBlazor"); _sb.AppendLine(); _sb.AppendLine("> Enterprise-class Blazor UI component library based on Bootstrap 5"); @@ -212,13 +207,9 @@ private static string TruncateSummary(string summary, int maxLength) return summary.Length <= maxLength ? summary : summary[..(maxLength - 3)] + "..."; } - /// - /// Build documentation for a single component - /// - public string BuildComponentDoc(ComponentInfo component) + public static string BuildComponentDoc(ComponentInfo component) { - _sb.Clear(); - + var _sb = new StringBuilder(); _sb.AppendLine($"# BootstrapBlazor {component.Name}"); _sb.AppendLine(); @@ -228,7 +219,7 @@ public string BuildComponentDoc(ComponentInfo component) _sb.AppendLine(); } - BuildComponentSection(component, includeHeader: false); + BuildComponentSection(_sb, component, includeHeader: false); // Footer _sb.AppendLine("---"); @@ -237,7 +228,7 @@ public string BuildComponentDoc(ComponentInfo component) return _sb.ToString(); } - private void BuildComponentSection(ComponentInfo component, bool includeHeader = true) + private static void BuildComponentSection(StringBuilder _sb, ComponentInfo component, bool includeHeader = true) { if (includeHeader) { @@ -359,33 +350,33 @@ private void BuildComponentSection(ComponentInfo component, bool includeHeader = } } - /// - /// Build a minimal parameter table for embedding in existing docs - /// - public string BuildParameterTable(List parameters) - { - _sb.Clear(); - - _sb.AppendLine("| Parameter | Type | Default | Description |"); - _sb.AppendLine("|-----------|------|---------|-------------|"); - - var sortedParams = parameters - .OrderByDescending(p => p.IsRequired) - .ThenBy(p => p.IsEventCallback) - .ThenBy(p => p.Name); - - foreach (var param in sortedParams) - { - var required = param.IsRequired ? " **[Required]**" : ""; - var description = EscapeMarkdownCell(param.Description ?? "") + required; - var defaultVal = param.DefaultValue ?? "-"; - var type = EscapeMarkdownCell(param.Type); - - _sb.AppendLine($"| {param.Name} | `{type}` | {defaultVal} | {description} |"); - } - - return _sb.ToString(); - } + ///// + ///// Build a minimal parameter table for embedding in existing docs + ///// + //public string BuildParameterTable(List parameters) + //{ + // _sb.Clear(); + + // _sb.AppendLine("| Parameter | Type | Default | Description |"); + // _sb.AppendLine("|-----------|------|---------|-------------|"); + + // var sortedParams = parameters + // .OrderByDescending(p => p.IsRequired) + // .ThenBy(p => p.IsEventCallback) + // .ThenBy(p => p.Name); + + // foreach (var param in sortedParams) + // { + // var required = param.IsRequired ? " **[Required]**" : ""; + // var description = EscapeMarkdownCell(param.Description ?? "") + required; + // var defaultVal = param.DefaultValue ?? "-"; + // var type = EscapeMarkdownCell(param.Type); + + // _sb.AppendLine($"| {param.Name} | `{type}` | {defaultVal} | {description} |"); + // } + + // return _sb.ToString(); + //} private static string EscapeMarkdownCell(string text) { diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/Program.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/Program.cs index 88eec57f..2153e101 100644 --- a/tools/BootstrapBlazor.LLMsDocsGenerator/Program.cs +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/Program.cs @@ -3,53 +3,6 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone -using LlmsDocsGenerator; -using System.CommandLine; +using BootstrapBlazor.LLMsDocsGenerator; -var componentOption = new Option("--component") { Description = "Generate documentation for a specific component only" }; -var indexOnlyOption = new Option("--index-only") { Description = "Generate only the index file (llms.txt)" }; -var checkOption = new Option("--check") { Description = "Check if documentation is up-to-date (for CI/CD)" }; -var rootFolderOption = new Option("--root") { Description = "Set the root folder of project" }; -var debugOption = new Option("--debug") { Description = "Set the environment to development and display debugging information." }; - -var rootCommand = new RootCommand("BootstrapBlazor LLMs Documentation Generator") -{ - componentOption, - indexOnlyOption, - checkOption, - rootFolderOption, - debugOption -}; - -rootCommand.SetAction(async result => -{ - var debug = result.GetValue(debugOption); - var rootFolder = result.GetValue(rootFolderOption); - var generator = new DocsGenerator(rootFolder, debug); - - var check = result.GetValue(checkOption); - if (check) - { - var isUpToDate = await generator.CheckAsync(); - Environment.ExitCode = isUpToDate ? 0 : 1; - return; - } - - var indexOnly = result.GetValue(indexOnlyOption); - if (indexOnly) - { - await generator.GenerateIndexAsync(); - return; - } - - var component = result.GetValue(componentOption); - if (!string.IsNullOrEmpty(component)) - { - await generator.GenerateComponentAsync(component); - return; - } - - await generator.GenerateAllAsync(); -}); - -return await rootCommand.Parse(args).InvokeAsync(); +await Runner.Run(args); diff --git a/tools/BootstrapBlazor.LLMsDocsGenerator/Runner.cs b/tools/BootstrapBlazor.LLMsDocsGenerator/Runner.cs new file mode 100644 index 00000000..b41f89c1 --- /dev/null +++ b/tools/BootstrapBlazor.LLMsDocsGenerator/Runner.cs @@ -0,0 +1,15 @@ +// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone + +namespace BootstrapBlazor.LLMsDocsGenerator; + +internal static class Runner +{ + public static async Task Run(string[] args) + { + var result = ArgumentsHelper.Parse(args); + + await result.InvokeAsync(); + } +}