374 lines
15 KiB
C#
374 lines
15 KiB
C#
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;
|
|
|
|
namespace NetDocsForLLM.Services
|
|
{
|
|
public interface IDocumentationGenerator
|
|
{
|
|
Task<DocumentationModel> GenerateDocumentation(IEnumerable<AssemblyModel> assemblies, ExportSettings settings);
|
|
string GenerateDocumentationPreview(DocumentationModel documentation, ExportSettings settings);
|
|
}
|
|
|
|
public class DocumentationGenerator : IDocumentationGenerator
|
|
{
|
|
private readonly IDocFxService _metadataService;
|
|
|
|
public DocumentationGenerator(IDocFxService metadataService)
|
|
{
|
|
_metadataService = metadataService ?? throw new ArgumentNullException(nameof(metadataService));
|
|
}
|
|
|
|
public async Task<DocumentationModel> GenerateDocumentation(IEnumerable<AssemblyModel> assemblies, ExportSettings settings)
|
|
{
|
|
try
|
|
{
|
|
// 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 JSON files produced by our reflection service
|
|
var jsonFiles = Directory.GetFiles(metadataPath, "*.json", SearchOption.AllDirectories);
|
|
foreach (var jsonFile in jsonFiles)
|
|
{
|
|
if (Path.GetFileName(jsonFile).StartsWith("error_"))
|
|
continue; // Skip error files
|
|
|
|
ProcessJsonMetadata(jsonFile, documentation, namespaces, settings);
|
|
}
|
|
|
|
return documentation;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException($"Error al generar documentación: {ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
private void ProcessJsonMetadata(string jsonFile, DocumentationModel documentation,
|
|
Dictionary<string, NamespaceDocumentation> namespaces,
|
|
ExportSettings settings)
|
|
{
|
|
try
|
|
{
|
|
// Leer el archivo JSON
|
|
var jsonContent = File.ReadAllText(jsonFile);
|
|
var typeData = JsonConvert.DeserializeObject<dynamic>(jsonContent);
|
|
|
|
if (typeData == null)
|
|
return;
|
|
|
|
// Obtener namespace
|
|
string namespaceName = typeData.Namespace?.ToString() ?? "Global";
|
|
|
|
// Buscar o crear el namespace en nuestra documentación
|
|
if (!namespaces.TryGetValue(namespaceName, out var namespaceDoc))
|
|
{
|
|
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)
|
|
};
|
|
|
|
// 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}";
|
|
}
|
|
|
|
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)
|
|
{
|
|
try
|
|
{
|
|
if (settings.OutputFormat == OutputFormat.Json)
|
|
{
|
|
return JsonConvert.SerializeObject(documentation, Formatting.Indented,
|
|
new JsonSerializerSettings
|
|
{
|
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
});
|
|
}
|
|
else // YAML
|
|
{
|
|
// 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,
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
}));
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException($"Error al generar vista previa: {ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
private string ConvertJsonToSimpleYaml(string json)
|
|
{
|
|
// 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}");
|
|
yaml.AppendLine($" fullName: {type.FullName}");
|
|
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: \"{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: \"{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}");
|
|
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");
|
|
}
|
|
}
|
|
} |