From 641bb7baf1b1d022dc1140dbd275d468fdd31bb1 Mon Sep 17 00:00:00 2001 From: Miguel Date: Wed, 26 Mar 2025 11:51:51 +0100 Subject: [PATCH] Cambio a Reflection porque la libreria DocFX no es compatible --- App.xaml | 11 +- App.xaml.cs | 4 +- Converters/EnumBooleanConverter.cs | 30 +-- Helpers/DocFxHelper.cs | 4 +- Helpers/JsonHelper.cs | 2 +- MainWindow.xaml | 8 +- MainWindow.xaml.cs | 4 +- Models/AssemblyModel.cs | 4 +- Models/DocumentationModel.cs | 2 +- Models/ExportSettings.cs | 2 +- Models/ReflectionAnalyzerService.cs | 343 ++++++++++++++++++++++++++++ NetDocsForLLM.csproj | 2 +- Services/AssemblyAnalyzer.cs | 2 +- Services/DocFxService.cs | 203 ---------------- Services/DocumentationGenerator.cs | 340 ++++++++++++++++++++------- Services/IDocFxService.cs | 20 ++ ViewModels/MainViewModel.cs | 2 +- 17 files changed, 658 insertions(+), 325 deletions(-) create mode 100644 Models/ReflectionAnalyzerService.cs delete mode 100644 Services/DocFxService.cs create mode 100644 Services/IDocFxService.cs diff --git a/App.xaml b/App.xaml index c173f89..6281595 100644 --- a/App.xaml +++ b/App.xaml @@ -1,13 +1,10 @@ - + - - + diff --git a/App.xaml.cs b/App.xaml.cs index bd89bd2..1987543 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using NetDocsForLLM.Services; using NetDocsForLLM.ViewModels; using System; @@ -20,7 +20,7 @@ namespace NetDocsForLLM private void ConfigureServices(ServiceCollection services) { // Register services - services.AddSingleton(); + services.AddSingleton(); // <- Cambio aquí services.AddSingleton(); services.AddSingleton(); diff --git a/Converters/EnumBooleanConverter.cs b/Converters/EnumBooleanConverter.cs index 21b40f6..7fa786f 100644 --- a/Converters/EnumBooleanConverter.cs +++ b/Converters/EnumBooleanConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Windows.Data; @@ -11,31 +11,25 @@ namespace NetDocsForLLM.Converters { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value == null || parameter == null) + if (parameter == null || value == null) return false; - string checkValue = parameter.ToString(); - string currentValue = value.ToString(); + string parameterString = parameter.ToString(); + if (Enum.IsDefined(value.GetType(), value)) + { + return value.ToString().Equals(parameterString, StringComparison.OrdinalIgnoreCase); + } - return checkValue.Equals(currentValue, StringComparison.InvariantCultureIgnoreCase); + return false; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - if (value == null || parameter == null) + if (parameter == null || !(bool)value) return null; - bool isChecked = (bool)value; - if (isChecked) - { - if (parameter is string parameterString) - { - return Enum.Parse(targetType, parameterString); - } - return parameter; - } - - return Binding.DoNothing; + string parameterString = parameter.ToString(); + return Enum.Parse(targetType, parameterString); } } -} +} \ No newline at end of file diff --git a/Helpers/DocFxHelper.cs b/Helpers/DocFxHelper.cs index 23590b5..3aaa35d 100644 --- a/Helpers/DocFxHelper.cs +++ b/Helpers/DocFxHelper.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.IO; +using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; -using System.Linq; namespace NetDocsForLLM.Helpers { diff --git a/Helpers/JsonHelper.cs b/Helpers/JsonHelper.cs index 2b47e3e..76a6169 100644 --- a/Helpers/JsonHelper.cs +++ b/Helpers/JsonHelper.cs @@ -1,4 +1,4 @@ -using NetDocsForLLM.Models; +using NetDocsForLLM.Models; using Newtonsoft.Json; using System; using System.IO; diff --git a/MainWindow.xaml b/MainWindow.xaml index ce5a9c2..851aa74 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -4,9 +4,15 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:NetDocsForLLM" + xmlns:conv="clr-namespace:NetDocsForLLM.Converters" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" mc:Ignorable="d" Title="NetDocs para LLMs" Height="650" Width="800"> + + + + + @@ -199,4 +205,4 @@ - \ No newline at end of file + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 6ebc39d..2d13e49 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System.Windows; namespace NetDocsForLLM { @@ -9,4 +9,4 @@ namespace NetDocsForLLM InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/Models/AssemblyModel.cs b/Models/AssemblyModel.cs index 8ceac34..653f7b6 100644 --- a/Models/AssemblyModel.cs +++ b/Models/AssemblyModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using CommunityToolkit.Mvvm.ComponentModel; @@ -48,4 +48,4 @@ namespace NetDocsForLLM.Models XmlDocPath = string.Empty; } } -} \ No newline at end of file +} diff --git a/Models/DocumentationModel.cs b/Models/DocumentationModel.cs index f4b1d24..19d17fc 100644 --- a/Models/DocumentationModel.cs +++ b/Models/DocumentationModel.cs @@ -1,4 +1,4 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; using System.Collections.ObjectModel; namespace NetDocsForLLM.Models diff --git a/Models/ExportSettings.cs b/Models/ExportSettings.cs index 35b8cd8..1967d0b 100644 --- a/Models/ExportSettings.cs +++ b/Models/ExportSettings.cs @@ -1,4 +1,4 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; namespace NetDocsForLLM.Models { diff --git a/Models/ReflectionAnalyzerService.cs b/Models/ReflectionAnalyzerService.cs new file mode 100644 index 0000000..8e4f3e7 --- /dev/null +++ b/Models/ReflectionAnalyzerService.cs @@ -0,0 +1,343 @@ +using NetDocsForLLM.Models; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace NetDocsForLLM.Services +{ + /// + /// Implementación del servicio de análisis de ensamblados basado en Reflection + /// + public class ReflectionAnalyzerService : IDocFxService + { + private readonly string _workingDirectory; + + public ReflectionAnalyzerService() + { + // Crear un directorio temporal para trabajar + _workingDirectory = Path.Combine(Path.GetTempPath(), "NetDocsForLLM_" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(_workingDirectory); + } + + public async Task GenerateMetadataAsync(IEnumerable assemblies) + { + // Crear directorio para metadatos + var metadataPath = Path.Combine(_workingDirectory, "metadata"); + Directory.CreateDirectory(metadataPath); + + // Para cada ensamblado, generar archivo de metadatos + foreach (var assembly in assemblies) + { + await Task.Run(() => ProcessAssembly(assembly, metadataPath)); + } + + return metadataPath; + } + + private void ProcessAssembly(AssemblyModel assemblyModel, string outputPath) + { + try + { + var assembly = assemblyModel.LoadedAssembly; + if (assembly == null) + throw new ArgumentException("El ensamblado no está cargado", nameof(assemblyModel)); + + // Cargar comentarios XML si existen + XDocument xmlDoc = null; + if (assemblyModel.HasXmlDocumentation && File.Exists(assemblyModel.XmlDocPath)) + { + try + { + xmlDoc = XDocument.Load(assemblyModel.XmlDocPath); + } + catch (Exception) + { + // Si hay error al cargar XML, continuar sin comentarios + } + } + + // Procesar todos los tipos exportados + foreach (var type in assembly.GetExportedTypes()) + { + try + { + // Generar archivo de metadatos para cada tipo + var typeInfo = new + { + Name = type.Name, + FullName = type.FullName, + Namespace = type.Namespace, + IsClass = type.IsClass, + IsInterface = type.IsInterface, + IsEnum = type.IsEnum, + IsAbstract = type.IsAbstract, + IsSealed = type.IsSealed, + IsPublic = type.IsPublic, + BaseType = type.BaseType?.FullName, + Interfaces = type.GetInterfaces().Select(i => i.FullName).ToArray(), + XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(type)), + Members = GetMembers(type, xmlDoc) + }; + + // Guardar en archivo JSON + string typePath = Path.Combine(outputPath, $"{type.FullName.Replace('+', '_')}.json"); + File.WriteAllText(typePath, JsonConvert.SerializeObject(typeInfo, Formatting.Indented)); + } + catch (Exception ex) + { + // Registrar error para este tipo pero continuar con otros + File.WriteAllText( + Path.Combine(outputPath, $"error_{type.FullName.Replace('+', '_')}.txt"), + $"Error procesando tipo {type.FullName}: {ex.Message}\n{ex.StackTrace}"); + } + } + } + catch (Exception ex) + { + // Registrar el error pero continuar con otros ensamblados + File.WriteAllText( + Path.Combine(outputPath, $"error_{Path.GetFileNameWithoutExtension(assemblyModel.FilePath)}.txt"), + $"Error procesando ensamblado: {ex.Message}\n{ex.StackTrace}"); + } + } + + private object[] GetMembers(Type type, XDocument xmlDoc) + { + var result = new List(); + + // Obtener métodos + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) + { + try + { + if (method.IsSpecialName) // Excluir getters/setters de propiedades + continue; + + var parameters = method.GetParameters().Select(p => new + { + Name = p.Name, + Type = GetFriendlyTypeName(p.ParameterType), + IsOptional = p.IsOptional, + DefaultValue = p.IsOptional ? ConvertDefaultValueToString(p.DefaultValue) : null + }).ToArray(); + + var methodInfo = new + { + Name = method.Name, + MemberType = "Method", + ReturnType = GetFriendlyTypeName(method.ReturnType), + IsStatic = method.IsStatic, + IsPublic = method.IsPublic, + Parameters = parameters, + XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(method)) + }; + + result.Add(methodInfo); + } + catch + { + // Ignorar métodos con problemas + continue; + } + } + + // Obtener propiedades + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) + { + try + { + var propertyInfo = new + { + Name = property.Name, + MemberType = "Property", + Type = GetFriendlyTypeName(property.PropertyType), + CanRead = property.CanRead, + CanWrite = property.CanWrite, + IsStatic = property.GetAccessors(true).FirstOrDefault()?.IsStatic ?? false, + XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(property)) + }; + + result.Add(propertyInfo); + } + catch + { + // Ignorar propiedades con problemas + continue; + } + } + + // Obtener eventos + foreach (var eventInfo in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) + { + try + { + var eventData = new + { + Name = eventInfo.Name, + MemberType = "Event", + EventHandlerType = GetFriendlyTypeName(eventInfo.EventHandlerType), + IsStatic = eventInfo.GetAddMethod()?.IsStatic ?? false, + XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(eventInfo)) + }; + + result.Add(eventData); + } + catch + { + // Ignorar eventos con problemas + continue; + } + } + + // Obtener campos + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) + { + try + { + var fieldInfo = new + { + Name = field.Name, + MemberType = "Field", + Type = GetFriendlyTypeName(field.FieldType), + IsStatic = field.IsStatic, + IsConstant = field.IsLiteral && !field.IsInitOnly, + Value = field.IsLiteral ? ConvertDefaultValueToString(field.GetValue(null)) : null, + XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(field)) + }; + + result.Add(fieldInfo); + } + catch + { + // Ignorar campos con problemas + continue; + } + } + + return result.ToArray(); + } + + private string GetFriendlyTypeName(Type type) + { + if (type == null) return "void"; + + if (type.IsGenericType) + { + var genericArgs = string.Join(", ", type.GetGenericArguments().Select(GetFriendlyTypeName)); + return $"{type.Name.Split('`')[0]}<{genericArgs}>"; + } + + // Usar nombres simplificados para tipos comunes + if (type == typeof(void)) return "void"; + if (type == typeof(int)) return "int"; + if (type == typeof(string)) return "string"; + if (type == typeof(bool)) return "bool"; + if (type == typeof(double)) return "double"; + if (type == typeof(float)) return "float"; + if (type == typeof(decimal)) return "decimal"; + if (type == typeof(long)) return "long"; + if (type == typeof(short)) return "short"; + if (type == typeof(byte)) return "byte"; + if (type == typeof(char)) return "char"; + if (type == typeof(object)) return "object"; + + return type.FullName ?? type.Name; + } + + private string ConvertDefaultValueToString(object defaultValue) + { + if (defaultValue == null) return "null"; + + // Para strings, agregar comillas + if (defaultValue is string stringValue) + return $"\"{stringValue}\""; + + return defaultValue.ToString(); + } + + private string GetMemberXmlId(MemberInfo member) + { + // Generar ID XML según el formato estándar de documentación XML + string prefix = ""; + if (member is MethodInfo) + prefix = "M:"; + else if (member is PropertyInfo) + prefix = "P:"; + else if (member is EventInfo) + prefix = "E:"; + else if (member is FieldInfo) + prefix = "F:"; + else if (member is Type) + prefix = "T:"; + + // Manejar tipos anidados + string declaringFullName; + if (member.DeclaringType != null) + { + declaringFullName = member.DeclaringType.FullName; + } + else if (member is Type typeInfo) + { + declaringFullName = typeInfo.FullName; + } + else + { + declaringFullName = "Unknown"; + } + + return prefix + declaringFullName + "." + member.Name; + } + + private object GetXmlDocumentation(XDocument xmlDoc, string memberId) + { + if (xmlDoc == null) + return null; + + try + { + // Buscar nodo de miembro por ID + var memberNode = xmlDoc.Descendants("member") + .FirstOrDefault(m => m.Attribute("name")?.Value == memberId); + + if (memberNode == null) + return null; + + // Extraer elementos de documentación + var summary = memberNode.Element("summary")?.Value.Trim(); + var remarks = memberNode.Element("remarks")?.Value.Trim(); + var returns = memberNode.Element("returns")?.Value.Trim(); + + var parameters = memberNode.Elements("param") + .Select(p => new + { + Name = p.Attribute("name")?.Value, + Description = p.Value.Trim() + }) + .Where(p => !string.IsNullOrEmpty(p.Name)) + .ToArray(); + + var examples = memberNode.Elements("example") + .Select(e => e.Value.Trim()) + .ToArray(); + + return new + { + Summary = summary, + Remarks = remarks, + Returns = returns, + Parameters = parameters, + Examples = examples + }; + } + catch + { + return null; + } + } + } +} \ No newline at end of file diff --git a/NetDocsForLLM.csproj b/NetDocsForLLM.csproj index d74862d..424a153 100644 --- a/NetDocsForLLM.csproj +++ b/NetDocsForLLM.csproj @@ -2,7 +2,7 @@ WinExe - net6.0-windows + net8.0-windows7.0 true enable diff --git a/Services/AssemblyAnalyzer.cs b/Services/AssemblyAnalyzer.cs index f6c679d..c66e2f9 100644 --- a/Services/AssemblyAnalyzer.cs +++ b/Services/AssemblyAnalyzer.cs @@ -1,4 +1,4 @@ -using NetDocsForLLM.Models; +using NetDocsForLLM.Models; using System; using System.IO; using System.Reflection; diff --git a/Services/DocFxService.cs b/Services/DocFxService.cs deleted file mode 100644 index 76905b5..0000000 --- a/Services/DocFxService.cs +++ /dev/null @@ -1,203 +0,0 @@ -using NetDocsForLLM.Models; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace NetDocsForLLM.Services -{ - public interface IDocFxService - { - Task GenerateMetadataAsync(IEnumerable assemblies); - } - - public class DocFxService : IDocFxService - { - private readonly string _workingDirectory; - private readonly string _docfxPath; - - public DocFxService() - { - // Create a temporary working directory - _workingDirectory = Path.Combine(Path.GetTempPath(), "NetDocsForLLM_" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(_workingDirectory); - - // Locate DocFX executable in the packages directory - var baseDir = AppDomain.CurrentDomain.BaseDirectory; - _docfxPath = Path.Combine(baseDir, "docfx", "docfx.exe"); - - // If not found in the default location, try to locate it in the packages directory - if (!File.Exists(_docfxPath)) - { - try - { - var packagesDir = Path.Combine(baseDir, "..", "..", "..", "packages"); - - // Check if packages directory exists before attempting to search it - if (Directory.Exists(packagesDir)) - { - var docfxPaths = Directory.GetFiles(packagesDir, "docfx.exe", SearchOption.AllDirectories); - if (docfxPaths.Length > 0) - { - _docfxPath = docfxPaths[0]; - } - } - - // Check embedded docfx in dotnet tools - if (!File.Exists(_docfxPath)) - { - var toolsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".dotnet", "tools"); - if (Directory.Exists(toolsDir)) - { - var docfxToolPath = Path.Combine(toolsDir, "docfx.exe"); - if (File.Exists(docfxToolPath)) - { - _docfxPath = docfxToolPath; - } - } - } - } - catch (DirectoryNotFoundException) - { - // Directory not found, continue to the check below - } - - // If still not found, throw an exception - if (!File.Exists(_docfxPath)) - { - throw new FileNotFoundException("No se pudo encontrar docfx.exe. Asegúrese de que el paquete docfx.console esté instalado."); - } - } - } - - public async Task GenerateMetadataAsync(IEnumerable assemblies) - { - try - { - // Create DocFX configuration - var configPath = Path.Combine(_workingDirectory, "docfx.json"); - var config = CreateDocFxConfig(assemblies); - File.WriteAllText(configPath, config); - - // Run DocFX metadata - var result = await RunDocFxMetadataAsync(configPath); - - // Return path to the generated metadata - var apiPath = Path.Combine(_workingDirectory, "obj", "api"); - return apiPath; - } - catch (Exception ex) - { - throw new InvalidOperationException($"Error al generar metadatos con DocFX: {ex.Message}", ex); - } - } - - private string CreateDocFxConfig(IEnumerable assemblies) - { - var assemblyPaths = new List(); - var xmlPaths = new List(); - - foreach (var assembly in assemblies) - { - assemblyPaths.Add(assembly.FilePath); - if (assembly.HasXmlDocumentation) - { - xmlPaths.Add(assembly.XmlDocPath); - } - } - - return $@" -{{ - ""metadata"": [ - {{ - ""src"": [ - {{ - ""files"": [ - ""{string.Join("\",\n \"", assemblyPaths.Select(p => p.Replace("\\", "\\\\")))}"" - ], - ""src"": ""."" - }} - ], - ""dest"": ""obj/api"", - ""properties"": {{ - ""TargetFramework"": ""net6.0"" - }}, - ""disableGitFeatures"": true, - ""disableDefaultFilter"": false - }} - ], - ""build"": {{ - ""content"": [ - {{ - ""files"": [""*.yml""], - ""src"": ""obj/api"", - ""dest"": ""api"" - }} - ], - ""resource"": [ - {{ - ""files"": [""images/**""], - ""exclude"": [""obj/**"", ""_site/**""] - }} - ], - ""dest"": ""_site"", - ""globalMetadataFiles"": [], - ""fileMetadataFiles"": [], - ""template"": [""default""], - ""postProcessors"": [], - ""markdownEngineName"": ""markdig"", - ""noLangKeyword"": false, - ""keepFileLink"": false, - ""cleanupCacheHistory"": false, - ""disableGitFeatures"": false - }} -}}"; - } - - private async Task RunDocFxMetadataAsync(string configPath) - { - var startInfo = new ProcessStartInfo - { - FileName = _docfxPath, - Arguments = $"metadata \"{configPath}\"", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = _workingDirectory - }; - - using var process = new Process { StartInfo = startInfo }; - var outputBuilder = new System.Text.StringBuilder(); - var errorBuilder = new System.Text.StringBuilder(); - - process.OutputDataReceived += (sender, e) => - { - if (!string.IsNullOrEmpty(e.Data)) - outputBuilder.AppendLine(e.Data); - }; - - process.ErrorDataReceived += (sender, e) => - { - if (!string.IsNullOrEmpty(e.Data)) - errorBuilder.AppendLine(e.Data); - }; - - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - await process.WaitForExitAsync(); - - if (process.ExitCode != 0) - { - throw new InvalidOperationException( - $"DocFX metadata falló con código de salida {process.ExitCode}. Error: {errorBuilder}"); - } - - return outputBuilder.ToString(); - } - } -} diff --git a/Services/DocumentationGenerator.cs b/Services/DocumentationGenerator.cs index 53d0352..3bdba3b 100644 --- a/Services/DocumentationGenerator.cs +++ b/Services/DocumentationGenerator.cs @@ -1,8 +1,9 @@ -using NetDocsForLLM.Models; +using NetDocsForLLM.Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; @@ -16,29 +17,32 @@ namespace NetDocsForLLM.Services public class DocumentationGenerator : IDocumentationGenerator { - private readonly IDocFxService _docFxService; + private readonly IDocFxService _metadataService; - public DocumentationGenerator(IDocFxService docFxService) + public DocumentationGenerator(IDocFxService metadataService) { - _docFxService = docFxService ?? throw new ArgumentNullException(nameof(docFxService)); + _metadataService = metadataService ?? throw new ArgumentNullException(nameof(metadataService)); } public async Task GenerateDocumentation(IEnumerable assemblies, ExportSettings settings) { try { - // Generate metadata with DocFX - var metadataPath = await _docFxService.GenerateMetadataAsync(assemblies); + // Generate metadata using reflection service + var metadataPath = await _metadataService.GenerateMetadataAsync(assemblies); // Process metadata files var documentation = new DocumentationModel(); var namespaces = new Dictionary(); - // Read YAML files produced by DocFX - var ymlFiles = Directory.GetFiles(metadataPath, "*.yml", SearchOption.AllDirectories); - foreach (var ymlFile in ymlFiles) + // Read JSON files produced by our reflection service + var jsonFiles = Directory.GetFiles(metadataPath, "*.json", SearchOption.AllDirectories); + foreach (var jsonFile in jsonFiles) { - ProcessYamlMetadata(ymlFile, documentation, namespaces, settings); + if (Path.GetFileName(jsonFile).StartsWith("error_")) + continue; // Skip error files + + ProcessJsonMetadata(jsonFile, documentation, namespaces, settings); } return documentation; @@ -49,62 +53,211 @@ namespace NetDocsForLLM.Services } } - private void ProcessYamlMetadata(string ymlFile, DocumentationModel documentation, - Dictionary namespaces, + private void ProcessJsonMetadata(string jsonFile, DocumentationModel documentation, + Dictionary namespaces, ExportSettings settings) { - // This is a simplified implementation. In a real application, - // you would need to use a YAML parser to read DocFX output - - // For this example, we'll create sample documentation data - var typeDoc = new TypeDocumentation + try { - Name = Path.GetFileNameWithoutExtension(ymlFile), - FullName = $"ExampleNamespace.{Path.GetFileNameWithoutExtension(ymlFile)}", - Description = "Descripción del tipo extraída de comentarios XML", - TypeKind = "Class" - }; + // Leer el archivo JSON + var jsonContent = File.ReadAllText(jsonFile); + var typeData = JsonConvert.DeserializeObject(jsonContent); - // Add some members - typeDoc.Members.Add(new MemberDocumentation - { - Name = "ExampleMethod", - Description = "Un método de ejemplo con documentación", - MemberType = "Method", - Signature = "public void ExampleMethod(string parameter1, int parameter2)", - ReturnType = "void", - ReturnDescription = "Este método no devuelve ningún valor" - }); + if (typeData == null) + return; - // Add parameters to the method - typeDoc.Members[0].Parameters.Add(new ParameterDocumentation - { - Name = "parameter1", - Type = "string", - Description = "Descripción del primer parámetro" - }); + // Obtener namespace + string namespaceName = typeData.Namespace?.ToString() ?? "Global"; - typeDoc.Members[0].Parameters.Add(new ParameterDocumentation - { - Name = "parameter2", - Type = "int", - Description = "Descripción del segundo parámetro" - }); - - // Add to namespace - var namespaceName = "ExampleNamespace"; - if (!namespaces.TryGetValue(namespaceName, out var namespaceDoc)) - { - namespaceDoc = new NamespaceDocumentation + // Buscar o crear el namespace en nuestra documentación + if (!namespaces.TryGetValue(namespaceName, out var namespaceDoc)) { - Name = namespaceName, - Description = "Descripción del namespace" + namespaceDoc = new NamespaceDocumentation + { + Name = namespaceName, + Description = $"Contiene tipos del ensamblado" + }; + namespaces[namespaceName] = namespaceDoc; + documentation.Namespaces.Add(namespaceDoc); + } + + // Crear documentación de tipo + var typeDoc = new TypeDocumentation + { + Name = typeData.Name, + FullName = typeData.FullName, + Description = typeData.XmlDocumentation?.Summary ?? "Sin documentación disponible", + TypeKind = GetTypeKind(typeData) }; - namespaces[namespaceName] = namespaceDoc; - documentation.Namespaces.Add(namespaceDoc); + + // Agregar base types e interfaces si están disponibles + if (typeData.BaseType != null && typeData.BaseType.ToString() != "System.Object") + { + typeDoc.BaseTypes.Add(typeData.BaseType.ToString()); + } + + // Agregar interfaces + if (typeData.Interfaces != null) + { + foreach (var interfaceType in typeData.Interfaces) + { + typeDoc.Interfaces.Add(interfaceType.ToString()); + } + } + + // Procesar miembros + if (typeData.Members != null) + { + foreach (var member in typeData.Members) + { + // Filtrar miembros privados si la configuración lo indica + if (!settings.IncludePrivateMembers && + member.IsPublic != null && !(bool)member.IsPublic) + continue; + + var memberDoc = new MemberDocumentation + { + Name = member.Name, + Description = member.XmlDocumentation?.Summary ?? "Sin documentación disponible", + MemberType = member.MemberType, + Signature = member.Signature ?? GenerateSignature(member), + ReturnType = GetReturnType(member), + ReturnDescription = member.XmlDocumentation?.Returns ?? "" + }; + + // Agregar parámetros si es un método + if (member.MemberType?.ToString() == "Method" && member.Parameters != null) + { + foreach (var param in member.Parameters) + { + var paramDoc = new ParameterDocumentation + { + Name = param.Name, + Type = param.Type, + IsOptional = param.IsOptional != null && (bool)param.IsOptional, + DefaultValue = param.DefaultValue?.ToString() ?? "", + Description = GetParameterDescription(member.XmlDocumentation, param.Name?.ToString()) + }; + + memberDoc.Parameters.Add(paramDoc); + } + } + + // Agregar ejemplos si están disponibles y configurados + if (settings.IncludeExamples && + member.XmlDocumentation?.Examples != null) + { + foreach (var example in member.XmlDocumentation.Examples) + { + if (example != null && !string.IsNullOrWhiteSpace(example.ToString())) + memberDoc.Examples.Add(example.ToString()); + } + } + + typeDoc.Members.Add(memberDoc); + } + } + + namespaceDoc.Types.Add(typeDoc); + } + catch (Exception ex) + { + // Si hay error al procesar, lo registramos pero continuamos + Console.WriteLine($"Error al procesar {jsonFile}: {ex.Message}"); + } + } + + private string GetTypeKind(dynamic typeData) + { + if (typeData.IsClass != null && (bool)typeData.IsClass) + return "Class"; + if (typeData.IsInterface != null && (bool)typeData.IsInterface) + return "Interface"; + if (typeData.IsEnum != null && (bool)typeData.IsEnum) + return "Enum"; + return "Type"; + } + + private string GenerateSignature(dynamic member) + { + // Generar una firma simple basada en el tipo de miembro + string memberType = member.MemberType?.ToString(); + string name = member.Name?.ToString() ?? "Unknown"; + + if (memberType == "Method") + { + string returnType = member.ReturnType?.ToString() ?? "void"; + string parameters = ""; + + if (member.Parameters != null) + { + var paramList = new List(); + foreach (var param in member.Parameters) + { + string paramType = param.Type?.ToString() ?? "object"; + string paramName = param.Name?.ToString() ?? "param"; + paramList.Add($"{paramType} {paramName}"); + } + parameters = string.Join(", ", paramList); + } + + return $"{returnType} {name}({parameters})"; + } + else if (memberType == "Property") + { + string propType = member.Type?.ToString() ?? "object"; + string accessors = ""; + + bool canRead = member.CanRead != null && (bool)member.CanRead; + bool canWrite = member.CanWrite != null && (bool)member.CanWrite; + + if (canRead && canWrite) + accessors = " { get; set; }"; + else if (canRead) + accessors = " { get; }"; + else if (canWrite) + accessors = " { set; }"; + + return $"{propType} {name}{accessors}"; + } + else if (memberType == "Field") + { + string fieldType = member.Type?.ToString() ?? "object"; + return $"{fieldType} {name}"; + } + else if (memberType == "Event") + { + string eventType = member.EventHandlerType?.ToString() ?? "EventHandler"; + return $"event {eventType} {name}"; } - namespaceDoc.Types.Add(typeDoc); + return name; + } + + private string GetReturnType(dynamic member) + { + string memberType = member.MemberType?.ToString(); + + if (memberType == "Method") + return member.ReturnType?.ToString() ?? "void"; + else if (memberType == "Property") + return member.Type?.ToString() ?? "object"; + + return ""; + } + + private string GetParameterDescription(dynamic xmlDocumentation, string paramName) + { + if (xmlDocumentation?.Parameters == null || paramName == null) + return ""; + + foreach (var param in xmlDocumentation.Parameters) + { + if (param.Name?.ToString() == paramName) + return param.Description?.ToString() ?? ""; + } + + return ""; } public string GenerateDocumentationPreview(DocumentationModel documentation, ExportSettings settings) @@ -113,24 +266,24 @@ namespace NetDocsForLLM.Services { if (settings.OutputFormat == OutputFormat.Json) { - return JsonConvert.SerializeObject(documentation, Formatting.Indented, + return JsonConvert.SerializeObject(documentation, Formatting.Indented, new JsonSerializerSettings { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore }); } else // YAML { - // Convert to JSON first, then to YAML (simplified) - var json = JsonConvert.SerializeObject(documentation, Formatting.None, + // En un escenario real, usaríamos YamlDotNet + // Para esta implementación, usaremos una conversión manual simplificada + return ConvertJsonToSimpleYaml( + JsonConvert.SerializeObject(documentation, Formatting.None, new JsonSerializerSettings { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore - }); - - // In a real application, you would use a YAML serializer - // For this example, we'll return a simple YAML representation - return ConvertJsonToSimpleYaml(json); + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore + })); } } catch (Exception ex) @@ -141,22 +294,19 @@ namespace NetDocsForLLM.Services private string ConvertJsonToSimpleYaml(string json) { - // This is a simplified conversion for demonstration purposes - // In a real application, you would use a YAML serializer library - // Deserialize JSON var obj = JsonConvert.DeserializeObject(json); - + // Basic YAML builder var yaml = new System.Text.StringBuilder(); yaml.AppendLine("namespaces:"); - + foreach (var ns in obj.Namespaces) { yaml.AppendLine($" - name: {ns.Name}"); yaml.AppendLine($" description: {ns.Description}"); yaml.AppendLine(" types:"); - + foreach (var type in ns.Types) { yaml.AppendLine($" - name: {type.Name}"); @@ -164,35 +314,61 @@ namespace NetDocsForLLM.Services yaml.AppendLine($" typeKind: {type.TypeKind}"); yaml.AppendLine($" description: {type.Description}"); yaml.AppendLine(" members:"); - + foreach (var member in type.Members) { yaml.AppendLine($" - name: {member.Name}"); yaml.AppendLine($" memberType: {member.MemberType}"); - yaml.AppendLine($" signature: {member.Signature}"); - yaml.AppendLine($" description: {member.Description}"); - - if (member.Parameters.Count > 0) + yaml.AppendLine($" signature: \"{EscapeYamlString(member.Signature)}\""); + yaml.AppendLine($" description: \"{EscapeYamlString(member.Description)}\""); + + if (member.Parameters != null && member.Parameters.Count > 0) { yaml.AppendLine(" parameters:"); foreach (var param in member.Parameters) { yaml.AppendLine($" - name: {param.Name}"); yaml.AppendLine($" type: {param.Type}"); - yaml.AppendLine($" description: {param.Description}"); + yaml.AppendLine($" description: \"{EscapeYamlString(param.Description)}\""); + yaml.AppendLine($" isOptional: {param.IsOptional.ToString().ToLower()}"); + if (!string.IsNullOrEmpty(param.DefaultValue)) + yaml.AppendLine($" defaultValue: \"{EscapeYamlString(param.DefaultValue)}\""); } } - + if (!string.IsNullOrEmpty(member.ReturnType)) { yaml.AppendLine($" returnType: {member.ReturnType}"); - yaml.AppendLine($" returnDescription: {member.ReturnDescription}"); + if (!string.IsNullOrEmpty(member.ReturnDescription)) + yaml.AppendLine($" returnDescription: \"{EscapeYamlString(member.ReturnDescription)}\""); + } + + if (member.Examples != null && member.Examples.Count > 0) + { + yaml.AppendLine(" examples:"); + foreach (var example in member.Examples) + { + yaml.AppendLine($" - |\n {example.Replace("\n", "\n ")}"); + } } } } } - + return yaml.ToString(); } + + private string EscapeYamlString(string input) + { + if (string.IsNullOrEmpty(input)) + return ""; + + return input + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\n", "\\n") + .Replace("\r", "\\r") + .Replace("\t", "\\t"); + } } -} +} \ No newline at end of file diff --git a/Services/IDocFxService.cs b/Services/IDocFxService.cs new file mode 100644 index 0000000..6b6153e --- /dev/null +++ b/Services/IDocFxService.cs @@ -0,0 +1,20 @@ +using NetDocsForLLM.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace NetDocsForLLM.Services +{ + /// + /// Interfaz para servicios de extracción de metadatos de ensamblados. + /// El nombre se mantiene por compatibilidad, aunque ya no usemos DocFx. + /// + public interface IDocFxService + { + /// + /// Genera metadatos de los ensamblados proporcionados + /// + /// Ensamblados para analizar + /// Ruta al directorio con los metadatos generados + Task GenerateMetadataAsync(IEnumerable assemblies); + } +} \ No newline at end of file diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 19e67aa..5889efb 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -1,4 +1,4 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using NetDocsForLLM.Models; using NetDocsForLLM.Services;