diff --git a/src/RepoM.ActionMenu.CodeGen/Constants.cs b/src/RepoM.ActionMenu.CodeGen/Constants.cs new file mode 100644 index 00000000..0e8c56c2 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Constants.cs @@ -0,0 +1,57 @@ +namespace RepoM.ActionMenu.CodeGen; + +using System.Collections.Generic; +using RepoM.ActionMenu.CodeGen.Models; + +internal static class Constants +{ + internal static readonly Dictionary TypeInfos = new() + { + { + typeof(Interface.YamlModel.Templating.Text).FullName!, + new TypeInfoDescriptor(nameof(Text), typeof(Interface.YamlModel.Templating.Text).FullName!) + { + Link = "repository_action_types.md#text", + } + }, + { + typeof(Interface.YamlModel.Templating.Predicate).FullName!, + new TypeInfoDescriptor(nameof(Interface.YamlModel.Templating.Predicate), typeof(Interface.YamlModel.Templating.Predicate).FullName!) + { + Link = "repository_action_types.md#predicate", + } + }, + { + typeof(Interface.YamlModel.ActionMenus.Context).FullName!, + new TypeInfoDescriptor(nameof(Interface.YamlModel.ActionMenus.Context), typeof(Interface.YamlModel.ActionMenus.Context).FullName!) + { + Link = "repository_action_types.md#context", + } + }, + { + typeof(Interface.YamlModel.ActionMenus.Context).FullName! + "?", + new TypeInfoDescriptor(nameof(Interface.YamlModel.ActionMenus.Context), typeof(Interface.YamlModel.ActionMenus.Context).FullName! + "?") + { + Link = "repository_action_types.md#context", + } + }, + }; + + /// + /// Project names of all the projects that are used in the code generation. + /// + public static readonly List Projects = + [ + "RepoM.ActionMenu.Interface", // this is for the description of the interface types and its members. + "RepoM.ActionMenu.Core", + + "RepoM.Plugin.AzureDevOps", + "RepoM.Plugin.Clipboard", + "RepoM.Plugin.Heidi", + "RepoM.Plugin.LuceneQueryParser", + "RepoM.Plugin.SonarCloud", + "RepoM.Plugin.Statistics", + "RepoM.Plugin.WebBrowser", + "RepoM.Plugin.WindowsExplorerGitInfo", + ]; +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Misc/FileSystemHelper.cs b/src/RepoM.ActionMenu.CodeGen/Misc/FileSystemHelper.cs index fd823197..619a889b 100644 --- a/src/RepoM.ActionMenu.CodeGen/Misc/FileSystemHelper.cs +++ b/src/RepoM.ActionMenu.CodeGen/Misc/FileSystemHelper.cs @@ -3,9 +3,9 @@ namespace RepoM.ActionMenu.CodeGen.Misc; using System; using System.IO; -public static class FileSystemHelper +internal static class FileSystemHelper { - public static void DeleteFileIsExist(string pathToGeneratedCode) + public static void DeleteFileIfExist(string pathToGeneratedCode) { if (!File.Exists(pathToGeneratedCode)) { @@ -23,7 +23,7 @@ public static void DeleteFileIsExist(string pathToGeneratedCode) } } - public static void CheckDirectory(string path) + public static void CheckDirectoryExists(string path) { if (!Directory.Exists(path)) { @@ -31,7 +31,7 @@ public static void CheckDirectory(string path) } } - public static void CheckFile(string path) + public static void CheckFileExists(string path) { if (!File.Exists(path)) { diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuClassDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuClassDescriptor.cs index 0fe65a27..6e36b1ed 100644 --- a/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuClassDescriptor.cs +++ b/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuClassDescriptor.cs @@ -9,7 +9,7 @@ public class ActionMenuClassDescriptor : ClassDescriptor /// /// Properties /// - public List ActionMenuProperties { get; set; } = new List(); + public List ActionMenuProperties { get; } = []; public string RepositoryActionName => Name; diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ClassDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ClassDescriptor.cs index 060f69e5..44c21c1b 100644 --- a/src/RepoM.ActionMenu.CodeGen/Models/ClassDescriptor.cs +++ b/src/RepoM.ActionMenu.CodeGen/Models/ClassDescriptor.cs @@ -20,7 +20,7 @@ public class ClassDescriptor : IXmlDocsExtended /// /// Properties, Functions, fields etc. etc. /// - public List Members { get; set; } = []; + public List Members { get; } = []; /// /// Friendly name @@ -31,6 +31,8 @@ public class ClassDescriptor : IXmlDocsExtended public string Namespace { get; set; } = null!; + public string FullName => $"{Namespace}.{ClassName}"; + public bool IsEnum => _symbolType == SymbolType.Enum; public bool IsClass => _symbolType == SymbolType.Class; diff --git a/src/RepoM.ActionMenu.CodeGen/Models/MemberDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/MemberDescriptor.cs index c8a4d5c9..1552c07d 100644 --- a/src/RepoM.ActionMenu.CodeGen/Models/MemberDescriptor.cs +++ b/src/RepoM.ActionMenu.CodeGen/Models/MemberDescriptor.cs @@ -8,13 +8,13 @@ public static class TypeInfoDescriptorFactory public static TypeInfoDescriptor Create(ITypeSymbol typeSymbol) { var displayString = typeSymbol.ToDisplayString(); - if (Program.TypeInfos.TryGetValue(displayString, out TypeInfoDescriptor? typeInfoDescriptor)) + if (Constants.TypeInfos.TryGetValue(displayString, out TypeInfoDescriptor? typeInfoDescriptor)) { return typeInfoDescriptor; } var result = new TypeInfoDescriptor(typeSymbol); - Program.TypeInfos.Add(displayString, result); + Constants.TypeInfos.Add(displayString, result); return result; } } diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ProjectDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ProjectDescriptor.cs index d1251d6c..d5f62a23 100644 --- a/src/RepoM.ActionMenu.CodeGen/Models/ProjectDescriptor.cs +++ b/src/RepoM.ActionMenu.CodeGen/Models/ProjectDescriptor.cs @@ -11,27 +11,37 @@ public sealed class ProjectDescriptor /// /// Assembly Name /// - public string AssemblyName { get; set; } = null!; + public required string AssemblyName { get; init; } /// /// Project name /// - public string ProjectName { get; set; } = null!; + public required string ProjectName { get; init; } + + /// + /// Full filename of the sln or csproj. + /// + public required string FullFilename { get; init; } + + /// + /// The directory of the project. + /// + public required string Directory { get; init; } /// /// List of class descriptors for repository actions. /// - public List ActionMenus { get; } = new(); + public List ActionMenus { get; } = []; /// /// List of class descriptors for context (ie scriban methods, properties) /// - public List ActionContextMenus { get; } = new(); + public List ActionContextMenus { get; } = []; /// - /// Regular types (to be used when action type has sub type property) + /// Regular types (to be used when action type has subtype property) /// - public List Types { get; } = new(); + public List Types { get; } = []; /// /// when project is plugin, the pluginname. @@ -51,8 +61,8 @@ public sealed class ProjectDescriptor /// /// is plugin or not. /// - public bool IsPlugin { get; private set; } = false; - + public bool IsPlugin { get; private set; } + [ScriptMemberIgnore] public void SetPackageInformation(PackageAttribute attribute) { diff --git a/src/RepoM.ActionMenu.CodeGen/ProcessMembersVisitor.cs b/src/RepoM.ActionMenu.CodeGen/ProcessMembersVisitor.cs index 969bfaed..efcf470e 100644 --- a/src/RepoM.ActionMenu.CodeGen/ProcessMembersVisitor.cs +++ b/src/RepoM.ActionMenu.CodeGen/ProcessMembersVisitor.cs @@ -20,11 +20,11 @@ public class ProcessMembersVisitor : IClassDescriptorVisitor // todo extend. private static readonly string[] _collectionTypes = - { - "System.Collections.Generic.List", - "System.Collections.Generic.IList", - "System.Collections.Generic.IEnumerable", - }; + [ + "System.Collections.Generic.List", + "System.Collections.Generic.IList", + "System.Collections.Generic.IEnumerable", + ]; public ProcessMembersVisitor(ITypeSymbol typeSymbol, IDictionary files) { diff --git a/src/RepoM.ActionMenu.CodeGen/Program.cs b/src/RepoM.ActionMenu.CodeGen/Program.cs index ab34c6bc..9402e828 100644 --- a/src/RepoM.ActionMenu.CodeGen/Program.cs +++ b/src/RepoM.ActionMenu.CodeGen/Program.cs @@ -8,7 +8,6 @@ namespace RepoM.ActionMenu.CodeGen; using Microsoft.CodeAnalysis; using RepoM.ActionMenu.CodeGen.Misc; using RepoM.ActionMenu.CodeGen.Models; -using RepoM.ActionMenu.Core.TestLib.Utils; using RepoM.ActionMenu.Interface.Attributes; using RepoM.ActionMenu.Interface.YamlModel; using RepoM.Core.Plugin.AssemblyInformation; @@ -16,141 +15,44 @@ namespace RepoM.ActionMenu.CodeGen; public static class Program { - internal static readonly Dictionary TypeInfos = new() - { - { - typeof(Interface.YamlModel.Templating.Text).FullName!, - new TypeInfoDescriptor(nameof(Text), typeof(Interface.YamlModel.Templating.Text).FullName!) - { - Link = "repository_action_types.md#text", - } - }, - { - typeof(Interface.YamlModel.Templating.Predicate).FullName!, - new TypeInfoDescriptor(nameof(Interface.YamlModel.Templating.Predicate), typeof(Interface.YamlModel.Templating.Predicate).FullName!) - { - Link = "repository_action_types.md#predicate", - } - }, - { - typeof(Interface.YamlModel.ActionMenus.Context).FullName!, - new TypeInfoDescriptor(nameof(Interface.YamlModel.ActionMenus.Context), typeof(Interface.YamlModel.ActionMenus.Context).FullName!) - { - Link = "repository_action_types.md#context", - } - }, - { - typeof(Interface.YamlModel.ActionMenus.Context).FullName! + "?", - new TypeInfoDescriptor(nameof(Interface.YamlModel.ActionMenus.Context), typeof(Interface.YamlModel.ActionMenus.Context).FullName! + "?") - { - Link = "repository_action_types.md#context", - } - }, - }; - public static async Task Main() { // var ns = typeSymbol.ContainingNamespace.ToDisplayString(); // var fullClassName = $"{ns}.{className}"; - var compile = new CompileRepoM(); - var rootFolder = ThisProjectAssembly.Info.GetSolutionDirectory(); - var srcFolder = Path.Combine(rootFolder, "src"); - var docsFolder = Path.Combine(rootFolder, "docs"); - var docsFolderSource = Path.Combine(docsFolder, "mdsource"); - - FileSystemHelper.CheckDirectory(srcFolder); - FileSystemHelper.CheckDirectory(docsFolder); - FileSystemHelper.CheckDirectory(Path.Combine(rootFolder, ".git")); - - var projects = new List - { - "RepoM.ActionMenu.Interface", // this is for the description of the interface types and its members. - "RepoM.ActionMenu.Core", - - "RepoM.Plugin.AzureDevOps", - "RepoM.Plugin.Clipboard", - "RepoM.Plugin.Heidi", - "RepoM.Plugin.LuceneQueryParser", - "RepoM.Plugin.SonarCloud", - "RepoM.Plugin.Statistics", - "RepoM.Plugin.WebBrowser", - "RepoM.Plugin.WindowsExplorerGitInfo", - }; - + + FileSystemHelper.CheckDirectoryExists(RepoMFolders.Source); + FileSystemHelper.CheckDirectoryExists(RepoMFolders.Documentation); + FileSystemHelper.CheckDirectoryExists(Path.Combine(RepoMFolders.Root, ".git")); + Template templateModule = await LoadTemplateAsync("Templates/ScribanModuleRegistration.scriban-cs"); Template templateDocs = await LoadTemplateAsync("Templates/DocsScriptVariables.scriban-txt"); Template templatePluginDocs = await LoadTemplateAsync("Templates/DocsPlugin.scriban-txt"); - Dictionary files = await LoadFilesAsync(rootFolder); - - var processedProjects = new Dictionary(); - + Dictionary snippetFiles = await LoadOldDocumentationSnippetFilesAsync(); Console.WriteLine("Compiling projects .."); - foreach (var project in projects) - { - var fullCsProjectFilename = Path.Combine(srcFolder, project, $"{project}.csproj"); - Console.WriteLine($" - {fullCsProjectFilename} .. "); - - FileSystemHelper.CheckFile(fullCsProjectFilename); - - ProjectDescriptor projectDescriptor = await CompileAndExtractProjectDescription(compile, fullCsProjectFilename, project, files); - processedProjects.Add(project, projectDescriptor); - Console.WriteLine(" done"); - } - + List processedProjects = await CompileProjectsAsync(snippetFiles); Console.WriteLine(string.Empty); - Dictionary> _allTypes2 = new(); - - foreach ((var projectName, ProjectDescriptor project) in processedProjects) - { - foreach (var classDescriptor in project.ActionMenus) - { - if (!_allTypes2.ContainsKey(classDescriptor.Namespace + "." + classDescriptor.ClassName)) - { - _allTypes2.Add(classDescriptor.Namespace + "." + classDescriptor.ClassName, new List()); - } - - foreach (var memberDescriptor in classDescriptor.ActionMenuProperties) - { - _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); - } - foreach (var memberDescriptor in classDescriptor.Members) - { - _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); - } - } + Console.WriteLine("Get all members from all projects"); + Dictionary> allMemberTypes = GetAllMembersFromProjects(processedProjects); + Console.WriteLine(string.Empty); - foreach (var classDescriptor in project.ActionContextMenus) - { - if (!_allTypes2.ContainsKey(classDescriptor.Namespace + "." + classDescriptor.ClassName)) - { - _allTypes2.Add(classDescriptor.Namespace + "." + classDescriptor.ClassName, new List()); - } - foreach (var memberDescriptor in classDescriptor.Members) - { - _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); - } - } + processedProjects.RemoveAll(p => p.ProjectName.Equals("RepoM.ActionMenu.Interface")); - foreach (var classDescriptor in project.Types) - { - if (!_allTypes2.ContainsKey(classDescriptor.Namespace + "." + classDescriptor.ClassName)) - { - _allTypes2.Add(classDescriptor.Namespace + "." + classDescriptor.ClassName, new List()); - } - foreach (var memberDescriptor in classDescriptor.Members) - { - _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); - } - } - } + Console.WriteLine("Update member type descriptions for all projects"); + UpdateMemberTypeDescriptions(processedProjects, allMemberTypes); + Console.WriteLine(string.Empty); - processedProjects.Remove("RepoM.ActionMenu.Interface"); + await GenerateOutputAsync(processedProjects, templatePluginDocs, templateDocs, templateModule); + } - // Copy descriptions from if (string.IsNullOrWhiteSpace(memberDescriptor.Description) && string.IsNullOrWhiteSpace(memberDescriptor.InheritDocs)) - foreach ((var projectName, ProjectDescriptor project) in processedProjects) + /// + /// Copy descriptions from if (string.IsNullOrWhiteSpace(memberDescriptor.Description) && string.IsNullOrWhiteSpace(memberDescriptor.InheritDocs)) + /// + private static void UpdateMemberTypeDescriptions(List processedProjects, Dictionary> allMemberTypes) + { + foreach (ProjectDescriptor project in processedProjects) { foreach (ActionMenuClassDescriptor classDescriptor in project.ActionMenus) { @@ -165,37 +67,60 @@ public static async Task Main() var className = memberDescriptor.InheritDocs[..index]; var typeName = memberDescriptor.InheritDocs[(index + 1)..]; - if (_allTypes2.TryGetValue(className, out List? xxx)) + if (!allMemberTypes.TryGetValue(className, out List? memberTypes)) { - MemberDescriptor? matchMemberDescriptor = xxx.SingleOrDefault(x => x.CSharpName == typeName); - if (matchMemberDescriptor != null) - { - memberDescriptor.Description = matchMemberDescriptor.Description; - } - else - { - Console.WriteLine("InheritDocs not found"); - } + throw new Exception("Cannot find Inherit docs type"); } - else + + MemberDescriptor? matchMemberDescriptor = memberTypes.SingleOrDefault(memberType => memberType.CSharpName == typeName); + if (matchMemberDescriptor == null) { - Console.WriteLine("InheritDocs not found"); + throw new Exception("Cannot find Inherit docs type"); } + + memberDescriptor.Description = matchMemberDescriptor.Description; } } } - + } + + private static async Task GenerateOutputAsync(List processedProjects, Template templatePluginDocs, Template templateDocs, Template templateModule) + { + await GenerateDocumentationAsync(processedProjects, templatePluginDocs, templateDocs); + await GenerateCodeAsync(processedProjects, templateModule); + } + + private static async Task GenerateCodeAsync(List processedProjects, Template templateModule) + { + // Generate module registration code in c#. + foreach (ProjectDescriptor project in processedProjects) + { + var fileName = Path.Combine(project.Directory, "RepoMCodeGen.generated.cs"); + + if (project.ActionContextMenus.Count == 0) + { + FileSystemHelper.DeleteFileIfExist(fileName); + continue; + } + + var content = await DocumentationGenerator.GetScribanInitializersCSharpCodeAsync(project.ActionContextMenus, templateModule).ConfigureAwait(false); + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + } + } + + private static async Task GenerateDocumentationAsync(List processedProjects, Template templatePluginDocs, Template templateDocs) + { // Generate plugin documentation - foreach ((var projectName, ProjectDescriptor? project) in processedProjects) + foreach (ProjectDescriptor project in processedProjects) { if (project.IsPlugin) { var name = project.ProjectName.ToLowerInvariant(); - var fileName = Path.Combine(docsFolderSource, $"plugin_{name}.generated.source.md"); + var fileName = Path.Combine(RepoMFolders.DocumentationMarkDownSource, $"plugin_{name}.generated.source.md"); var content = await DocumentationGenerator.GetPluginDocsContentAsync(project, templatePluginDocs).ConfigureAwait(false); await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); - fileName = Path.Combine(docsFolder, $"plugin_{name}.generated.md"); + fileName = Path.Combine(RepoMFolders.Documentation, $"plugin_{name}.generated.md"); if (!File.Exists(fileName)) { await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); @@ -204,11 +129,11 @@ public static async Task Main() else { // core - var fileName = Path.Combine(docsFolderSource, "repom.generated.source.md"); + var fileName = Path.Combine(RepoMFolders.DocumentationMarkDownSource, "repom.generated.source.md"); var content = await DocumentationGenerator.GetPluginDocsContentAsync(project, templatePluginDocs).ConfigureAwait(false); await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); - fileName = Path.Combine(docsFolder, "repom.generated.md"); + fileName = Path.Combine(RepoMFolders.Documentation, "repom.generated.md"); if (!File.Exists(fileName)) { await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); @@ -217,48 +142,84 @@ public static async Task Main() } // Generate module site documentation - foreach ((var projectName, ProjectDescriptor? project) in processedProjects) + foreach (ProjectDescriptor project in processedProjects) { foreach (ActionMenuContextClassDescriptor actionContextMenu in project.ActionContextMenus) { var name = actionContextMenu.Name.ToLowerInvariant(); - var fileName = Path.Combine(docsFolderSource, $"script_variables_{name}.generated.source.md"); + var fileName = Path.Combine(RepoMFolders.DocumentationMarkDownSource, $"script_variables_{name}.generated.source.md"); var content = await DocumentationGenerator.GetDocsContentAsync(actionContextMenu, templateDocs).ConfigureAwait(false); await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); - fileName = Path.Combine(docsFolder, $"script_variables_{name}.generated.md"); + fileName = Path.Combine(RepoMFolders.Documentation, $"script_variables_{name}.generated.md"); if (!File.Exists(fileName)) { await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); } } } + } - // Generate module registration code in c#. - foreach ((var projectName, ProjectDescriptor? project) in processedProjects) + private static Dictionary> GetAllMembersFromProjects(List processedProjects) + { + Dictionary> allMemberTypes = new(); + + foreach (ProjectDescriptor project in processedProjects) { - var fileName = Path.Combine(srcFolder, projectName, "RepoMCodeGen.generated.cs"); + foreach (ActionMenuClassDescriptor classDescriptor in project.ActionMenus) + { + allMemberTypes.TryAdd(classDescriptor.FullName, []); + allMemberTypes[classDescriptor.FullName].AddRange(classDescriptor.ActionMenuProperties); + allMemberTypes[classDescriptor.FullName].AddRange(classDescriptor.Members); + } - if (project.ActionContextMenus.Count == 0) + foreach (ActionMenuContextClassDescriptor classDescriptor in project.ActionContextMenus) { - FileSystemHelper.DeleteFileIsExist(fileName); - continue; + allMemberTypes.TryAdd(classDescriptor.FullName, []); + allMemberTypes[classDescriptor.FullName].AddRange(classDescriptor.Members); } - var content = await DocumentationGenerator.GetScribanInitializersCSharpCodeAsync(project.ActionContextMenus, templateModule).ConfigureAwait(false); - await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + foreach (ClassDescriptor classDescriptor in project.Types) + { + allMemberTypes.TryAdd(classDescriptor.FullName, []); + allMemberTypes[classDescriptor.FullName].AddRange(classDescriptor.Members); + } } + + return allMemberTypes; } - public static async Task CompileAndExtractProjectDescription(CompileRepoM compile, string pathToSolution, string project, IDictionary files) + private static async Task> CompileProjectsAsync(Dictionary files) + { + List processedProjects = new(Constants.Projects.Count); + var compile = new CompileRepoM(); + + foreach (var project in Constants.Projects) + { + var fullCsProjectFilename = Path.Combine(RepoMFolders.Source, project, $"{project}.csproj"); + Console.WriteLine($" - {fullCsProjectFilename} .. "); + + FileSystemHelper.CheckFileExists(fullCsProjectFilename); + + ProjectDescriptor projectDescriptor = await CompileAndExtractProjectDescriptionAsync(compile, fullCsProjectFilename, project, files); + processedProjects.Add(projectDescriptor); + Console.WriteLine(" done"); + } + + return processedProjects; + } + + public static async Task CompileAndExtractProjectDescriptionAsync(CompileRepoM compile, string pathToSolution, string project, IDictionary files) { Compilation compilation = await compile.CompileAsync(pathToSolution, project).ConfigureAwait(false); var projectDescriptor = new ProjectDescriptor - { - AssemblyName = compilation.AssemblyName ?? throw new Exception("Could not determine AssemblyName"), - ProjectName = project, - }; + { + AssemblyName = compilation.AssemblyName ?? throw new Exception("Could not determine AssemblyName"), + ProjectName = project, + FullFilename = pathToSolution, + Directory = Path.GetDirectoryName(pathToSolution) ?? throw new Exception("Could not determine Directory"), + }; AttributeData? assemblyAttribute = compilation.Assembly.GetAttributes().SingleOrDefault(x => x.AttributeClass?.Name == nameof(PackageAttribute)); if (assemblyAttribute != null) @@ -335,18 +296,18 @@ public static async Task