Cambio a Reflection porque la libreria DocFX no es compatible

This commit is contained in:
Miguel 2025-03-26 11:51:51 +01:00
parent 93b0f39a24
commit 641bb7baf1
17 changed files with 658 additions and 325 deletions

View File

@ -1,13 +1,10 @@
<Application x:Class="NetDocsForLLM.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NetDocsForLLM"
xmlns:converters="clr-namespace:NetDocsForLLM.Converters">
<Application x:Class="NetDocsForLLM.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:NetDocsForLLM"
xmlns:converters="clr-namespace:NetDocsForLLM.Converters">
<Application.Resources>
<ResourceDictionary>
<!-- Converters -->
<converters:EnumBooleanConverter x:Key="EnumBooleanConverter"/>
<converters:EnumBooleanConverter x:Key="EnumBooleanConverter" />
<!-- Other resources -->
</ResourceDictionary>
</Application.Resources>

View File

@ -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<IDocFxService, DocFxService>();
services.AddSingleton<IDocFxService, ReflectionAnalyzerService>(); // <- Cambio aquí
services.AddSingleton<IDocumentationGenerator, DocumentationGenerator>();
services.AddSingleton<IAssemblyAnalyzer, AssemblyAnalyzer>();

View File

@ -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);
}
}
}
}

View File

@ -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
{

View File

@ -1,4 +1,4 @@
using NetDocsForLLM.Models;
using NetDocsForLLM.Models;
using Newtonsoft.Json;
using System;
using System.IO;

View File

@ -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">
<Window.Resources>
<conv:EnumBooleanConverter x:Key="EnumBooleanConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@ -199,4 +205,4 @@
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
</Window>

View File

@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
namespace NetDocsForLLM
{
@ -9,4 +9,4 @@ namespace NetDocsForLLM
InitializeComponent();
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -1,4 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace NetDocsForLLM.Models

View File

@ -1,4 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace NetDocsForLLM.Models
{

View File

@ -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
{
/// <summary>
/// Implementación del servicio de análisis de ensamblados basado en Reflection
/// </summary>
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<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> 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<object>();
// 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;
}
}
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,4 +1,4 @@
using NetDocsForLLM.Models;
using NetDocsForLLM.Models;
using System;
using System.IO;
using System.Reflection;

View File

@ -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<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> 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<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> 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<AssemblyModel> assemblies)
{
var assemblyPaths = new List<string>();
var xmlPaths = new List<string>();
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<string> 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();
}
}
}

View File

@ -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<DocumentationModel> GenerateDocumentation(IEnumerable<AssemblyModel> 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<string, NamespaceDocumentation>();
// 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<string, NamespaceDocumentation> namespaces,
private void ProcessJsonMetadata(string jsonFile, DocumentationModel documentation,
Dictionary<string, NamespaceDocumentation> 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<dynamic>(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<string>();
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<dynamic>(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");
}
}
}
}

20
Services/IDocFxService.cs Normal file
View File

@ -0,0 +1,20 @@
using NetDocsForLLM.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NetDocsForLLM.Services
{
/// <summary>
/// Interfaz para servicios de extracción de metadatos de ensamblados.
/// El nombre se mantiene por compatibilidad, aunque ya no usemos DocFx.
/// </summary>
public interface IDocFxService
{
/// <summary>
/// Genera metadatos de los ensamblados proporcionados
/// </summary>
/// <param name="assemblies">Ensamblados para analizar</param>
/// <returns>Ruta al directorio con los metadatos generados</returns>
Task<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> assemblies);
}
}

View File

@ -1,4 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NetDocsForLLM.Models;
using NetDocsForLLM.Services;