diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..1803ed3 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,28 @@ + +[*] + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preserve_single_line_blocks = true + +# ReSharper properties +resharper_blank_lines_after_block_statements = 0 +resharper_blank_lines_after_using_list = 1 +resharper_blank_lines_around_block_case_section = 1 +resharper_csharp_blank_lines_around_field = 0 +resharper_csharp_blank_lines_around_invocable = 0 +resharper_csharp_blank_lines_around_namespace = 0 +resharper_csharp_blank_lines_around_single_line_invocable = 1 +resharper_csharp_blank_lines_inside_region = 0 +resharper_csharp_max_line_length = 600 +resharper_keep_existing_attribute_arrangement = true +resharper_keep_existing_enum_arrangement = false +resharper_max_attribute_length_for_same_line = 70 +resharper_place_expr_method_on_single_line = true +resharper_place_expr_property_on_single_line = true +resharper_place_simple_embedded_statement_on_same_line = false +resharper_place_simple_initializer_on_single_line = false +resharper_remove_blank_lines_near_braces_in_code = false +resharper_space_within_single_line_array_initializer_braces = false +resharper_wrap_object_and_collection_initializer_style = chop_always +configure_await_analysis_mode = library \ No newline at end of file diff --git a/src/PluginMerge/Commands/BaseCommand.cs b/src/PluginMerge/Commands/BaseCommand.cs index 870830b..ea42742 100644 --- a/src/PluginMerge/Commands/BaseCommand.cs +++ b/src/PluginMerge/Commands/BaseCommand.cs @@ -2,19 +2,33 @@ namespace PluginMerge.Commands; -public class BaseCommand : ICommand +public abstract class BaseCommand : ICommand { - [Option('d', "debug", Required = false, HelpText = "Enable debug log output")] + [Option('d', "debug", Required = false, HelpText = "Enable debug log output", Hidden = true)] public bool Debug { get; set; } + [Option('l', "log", Required = false, HelpText = "Log Level for output")] + public LogLevel? LoggerLevel { get; set; } + public int CloseCode { get; protected set; } = Constants.CloseCodes.NoError; - protected ILogger Logger; + protected ILogger Logger { get; private set; } public virtual Task Execute() { - LogHandler.InitLogger(Debug ? LogLevel.Debug : LogLevel.Information); - Logger = this.GetLogger(); + LogLevel level = LogLevel.Information; + if (Debug) + { + level = LogLevel.Debug; + } + + if (LoggerLevel.HasValue) + { + level = LoggerLevel.Value; + } + + LogBuilder.InitLogger(level); + Logger = LogBuilder.GetLogger(); return Task.CompletedTask; } } \ No newline at end of file diff --git a/src/PluginMerge/Commands/InitCommand.cs b/src/PluginMerge/Commands/InitCommand.cs index 3ad74a1..86034f7 100644 --- a/src/PluginMerge/Commands/InitCommand.cs +++ b/src/PluginMerge/Commands/InitCommand.cs @@ -4,7 +4,7 @@ namespace PluginMerge.Commands; [Verb("init", HelpText = "Creates a new merge.json configuration in the current directory")] -public class InitCommand : BaseCommand +public class InitCommand : BaseCommand { [Option('p', "path", Required = false, HelpText = "Path to create the merge.json configuration file in", Default = "./")] public string FilePath { get; set; } = "./"; @@ -14,12 +14,12 @@ public class InitCommand : BaseCommand public override async Task Execute() { - await base.Execute(); + await base.Execute().ConfigureAwait(false); try { string path = Path.Combine(FilePath, FileName).ToFullPath(); - await PluginMergeConfigHandler.Instance.Create(path); + await PluginMergeConfigHandler.Instance.Create(path).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/PluginMerge/Commands/MergeCommand.cs b/src/PluginMerge/Commands/MergeCommand.cs index 4cf9bcc..11f3021 100644 --- a/src/PluginMerge/Commands/MergeCommand.cs +++ b/src/PluginMerge/Commands/MergeCommand.cs @@ -4,7 +4,7 @@ namespace PluginMerge.Commands; [Verb("merge", true, HelpText = "Merges multiple .cs files into a single plugin/framework file.")] -public class MergeCommand : BaseCommand +public class MergeCommand : BaseCommand { [Option('p', "path", Required = false, HelpText = "Path to the merge.json configuration file", Default = "./merge.yml")] public string ConfigPath { get; set; } = "./merge.yml"; @@ -17,22 +17,59 @@ public class MergeCommand : BaseCommand [Option('o', "output", HelpText = "Additional output paths for the generated code file")] public IEnumerable OutputPaths { get; set; } - + public override async Task Execute() { - await base.Execute(); + await base.Execute().ConfigureAwait(false); + + PluginMergeConfig config = await LoadConfig().ConfigureAwait(false); + if (config is null) + { + return; + } - PluginMergeConfig config; + bool success = await RunMerge(config).ConfigureAwait(false); + if (!success) + { + return; + } + + await RunCompile(config).ConfigureAwait(false); + } + + private async Task RunMerge(PluginMergeConfig config) + { + if (!Merge) + { + return true; + } + try + { + MergeHandler handler = new(config); + await handler.Run().ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogCritical(ex, "An error occured merging files"); + CloseCode = Constants.CloseCodes.MergeFilesError; + return false; + } + + return true; + } + + private async Task LoadConfig() + { try { string configFile = ConfigPath.ToFullPath(); Logger.LogInformation("Loading Plugin Merge Config At {File}", configFile); - config = await PluginMergeConfigHandler.Instance.Load(configFile); - if (config == null) + PluginMergeConfig config = await PluginMergeConfigHandler.Instance.Load(configFile).ConfigureAwait(false); + if (config is null) { CloseCode = Constants.CloseCodes.MergeConfigNotFoundError; - return; + return null; } string path = Path.GetDirectoryName(configFile); @@ -41,48 +78,40 @@ public override async Task Execute() Directory.SetCurrentDirectory(path); } - if (OutputPaths != null) + if (OutputPaths is not null) { config.Merge.OutputPaths.AddRange(OutputPaths); } + + return config; } catch (Exception ex) { Logger.LogCritical(ex, "An error occured loading the config file"); CloseCode = Constants.CloseCodes.MergeConfigError; - return; + return null; } + } - if (Merge) + private async Task RunCompile(PluginMergeConfig config) + { + if (!Compile) { - try - { - MergeHandler handler = new(config); - await handler.Run(); - } - catch (Exception ex) - { - Logger.LogCritical(ex, "An error occured merging files"); - CloseCode = Constants.CloseCodes.MergeFilesError; - return; - } + return; } - if (Compile) + try { - try - { - CompileHandler compile = new(config.Merge.FinalFiles.FirstOrDefault(), config); - await compile.Run(); - } - catch (Exception ex) - { - Logger.LogCritical(ex, "An error compiling merged file"); - CloseCode = Constants.CloseCodes.CompileFilesError; - } + CompileHandler compile = new(config.Merge.FinalFiles.FirstOrDefault(), config); + await compile.Run().ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogCritical(ex, "An error compiling merged file"); + CloseCode = Constants.CloseCodes.CompileFilesError; } } - + [Usage(ApplicationAlias = "plugin.merge")] public static IEnumerable Examples => new List { diff --git a/src/PluginMerge/Commands/RenameCommand.cs b/src/PluginMerge/Commands/RenameCommand.cs new file mode 100644 index 0000000..b3cd0c0 --- /dev/null +++ b/src/PluginMerge/Commands/RenameCommand.cs @@ -0,0 +1,22 @@ +using CommandLine; +using PluginMerge.Rename; + +namespace PluginMerge.Commands; + +[Verb("rename", HelpText = "Renames a framework class name to match the plugin class name")] +public class RenameCommand : BaseCommand +{ + [Option('f', "file", Required = true, HelpText = "Path to the framework to rename")] + public string FileName { get; set; } + + [Option('n', "name", Required = true, HelpText = "Name to change the framework to")] + public string PluginName { get; set; } + + public override async Task Execute() + { + await base.Execute().ConfigureAwait(false); + + RenameHandler handler = new(FileName, PluginName); + CloseCode = await handler.Run().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/PluginMerge/Compile/CompileHandler.cs b/src/PluginMerge/Compile/CompileHandler.cs index 58d9da2..82f0083 100644 --- a/src/PluginMerge/Compile/CompileHandler.cs +++ b/src/PluginMerge/Compile/CompileHandler.cs @@ -2,7 +2,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; -using PluginMerge.Scanner; namespace PluginMerge.Compile; @@ -15,7 +14,7 @@ public class CompileHandler public CompileHandler(string fileName, PluginMergeConfig config) { - _logger = this.GetLogger(); + _logger = LogBuilder.GetLogger(); _fileName = fileName; _config = config; _compile = config.Compile; @@ -24,7 +23,7 @@ public CompileHandler(string fileName, PluginMergeConfig config) public async Task Run() { Stopwatch sw = Stopwatch.StartNew(); - _logger.LogInformation("Starting Merged File Compilation"); + _logger.LogInformation("Starting Merged File Compilation Version: {Version}", typeof(Program).Assembly.GetName().Version); if (!File.Exists(_fileName)) { @@ -32,16 +31,16 @@ public async Task Run() return; } - string code = await File.ReadAllTextAsync(_fileName); + string code = await File.ReadAllTextAsync(_fileName).ConfigureAwait(false); SyntaxTree tree = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(_config.PlatformSettings.Lang)); FileScanner scanner = new(_compile.AssemblyPaths, "*.dll", _compile.IgnorePaths, _compile.IgnoreFiles); List references = new(); - foreach ((string fileName, string _) in scanner.ScanFiles()) + foreach (ScannedFile file in scanner.ScanFiles()) { - _logger.LogDebug("Added Assembly Reference: {File}", fileName); - references.Add(MetadataReference.CreateFromFile(fileName)); + _logger.LogDebug("Added Assembly Reference: {File}", file.FileName); + references.Add(MetadataReference.CreateFromFile(file.FileName)); } await using MemoryStream stream = new(); diff --git a/src/PluginMerge/Configuration/CodeStyleConfig.cs b/src/PluginMerge/Configuration/CodeStyleConfig.cs index f868f64..454b0fb 100644 --- a/src/PluginMerge/Configuration/CodeStyleConfig.cs +++ b/src/PluginMerge/Configuration/CodeStyleConfig.cs @@ -26,7 +26,7 @@ public class CodeStyleConfig [YamlMember(Alias = "Write The Relative File Path In Region", Description = "Adds the code file path in a region")] public bool WriteFileRegion { get; set; } = true; - [JsonPropertyName("Keep Comments Comments")] + [JsonPropertyName("Keep Code Comments")] [YamlMember(Alias = "Keep Code Comments", Description = "Adds the code file path in a region")] public bool KeepComments { get; set; } = true; diff --git a/src/PluginMerge/Configuration/MergeConfig.cs b/src/PluginMerge/Configuration/MergeConfig.cs index e47f2cb..5d264d8 100644 --- a/src/PluginMerge/Configuration/MergeConfig.cs +++ b/src/PluginMerge/Configuration/MergeConfig.cs @@ -8,15 +8,15 @@ public class MergeConfig [JsonPropertyName("Plugin Name")] [YamlMember(Alias = "Plugin Name", Description = "Outputted plugin name")] public string PluginName { get; set; } - - [JsonPropertyName("Plugin Base Class")] - [YamlMember(Alias = "Plugin Base Class", Description = "Outputted plugin base class")] - public string BaseClass { get; set; } - + [JsonConverter(typeof(JsonStringEnumConverter))] [JsonPropertyName("Creator Mode")] [YamlMember(Alias = "Creator Mode", Description = "Which type of file to output (Plugin, Framework, or MergeFramework)")] public CreatorMode CreatorMode { get; set; } + + [JsonPropertyName("Namespace Override")] + [YamlMember(Alias = "Namespace Override", Description = "Overrides the default namespace")] + public string NamespaceOverride { get; set; } [JsonPropertyName("Plugin Input Paths")] [YamlMember(Alias = "Plugin Input Paths", Description = "Paths to use when reading in source code relative to the merge config")] @@ -54,10 +54,12 @@ public class MergeConfig [YamlIgnore] public IEnumerable FinalFiles => OutputPaths.Select(p => Path.Combine(p, $"{PluginName}.cs").ToFullPath()); + private bool ShouldSerializeNamespaceOverride() => CreatorMode == CreatorMode.MergeFramework; + public void Initialize() { PluginName ??= "MyPluginName"; - BaseClass ??= "CovalencePlugin"; + NamespaceOverride ??= string.Empty; InputPaths ??= new List { "./" }; OutputPaths ??= new List {"./build"}; Defines ??= new List { "DEBUG" }; diff --git a/src/PluginMerge/Configuration/PlatformSettings.cs b/src/PluginMerge/Configuration/PlatformSettings.cs index 96e6498..e188d0b 100644 --- a/src/PluginMerge/Configuration/PlatformSettings.cs +++ b/src/PluginMerge/Configuration/PlatformSettings.cs @@ -45,4 +45,14 @@ public static PlatformSettings GetPlatformSettings(Platform platform) _ => null }; } + + public static PlatformSettings GetCustomPlatformSettings(Platform platform, string @namespace) + { + return platform switch + { + Platform.Oxide => new PlatformSettings(@namespace, Oxide.Lang), + Platform.uMod => new PlatformSettings(@namespace, uMod.Lang), + _ => null + }; + } } \ No newline at end of file diff --git a/src/PluginMerge/Configuration/PluginMergeConfig.cs b/src/PluginMerge/Configuration/PluginMergeConfig.cs index b85a52f..d37cb08 100644 --- a/src/PluginMerge/Configuration/PluginMergeConfig.cs +++ b/src/PluginMerge/Configuration/PluginMergeConfig.cs @@ -19,16 +19,16 @@ public class PluginMergeConfig [JsonIgnore] [YamlIgnore] - public PlatformSettings PlatformSettings; + public PlatformSettings PlatformSettings { get; set; } public void Initialize() { - PlatformSettings = PlatformSettings.GetPlatformSettings(Platform); - Merge ??= new MergeConfig(); Merge.Initialize(); Compile ??= new CompileConfig(); Compile.Initialize(); + + PlatformSettings = string.IsNullOrEmpty(Merge.NamespaceOverride) ? PlatformSettings.GetPlatformSettings(Platform) : PlatformSettings.GetCustomPlatformSettings(Platform, Merge.NamespaceOverride); } } \ No newline at end of file diff --git a/src/PluginMerge/Configuration/PluginMergeConfigHandler.cs b/src/PluginMerge/Configuration/PluginMergeConfigHandler.cs index 95551b3..b060bf0 100644 --- a/src/PluginMerge/Configuration/PluginMergeConfigHandler.cs +++ b/src/PluginMerge/Configuration/PluginMergeConfigHandler.cs @@ -9,12 +9,12 @@ public class PluginMergeConfigHandler private PluginMergeConfigHandler() { - _logger = this.GetLogger(); + _logger = LogBuilder.GetLogger(); } public async Task Create(string file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file is null) throw new ArgumentNullException(nameof(file)); if (File.Exists(file)) { @@ -25,14 +25,14 @@ public async Task Create(string file) PluginMergeConfig config = new(); config.Initialize(); - await WriteConfig(file, config); + await WriteConfig(file, config).ConfigureAwait(false); _logger.LogInformation("Successfully created merge config at path: {File}", file); } public async Task Load(string file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file is null) throw new ArgumentNullException(nameof(file)); if (!File.Exists(file)) { @@ -40,9 +40,9 @@ public async Task Load(string file) return null; } - PluginMergeConfig config = await ReadConfig(file); + PluginMergeConfig config = await ReadConfig(file).ConfigureAwait(false); config.Initialize(); - await WriteConfig(file, config); + await WriteConfig(file, config).ConfigureAwait(false); return config; } @@ -50,13 +50,13 @@ public async Task Load(string file) private async Task WriteConfig(string file, PluginMergeConfig config) { IConfigSerializer serializer = GetSerializerForFile(file); - await File.WriteAllTextAsync(file, serializer.Serialize(config)); + await File.WriteAllTextAsync(file, serializer.Serialize(config)).ConfigureAwait(false); } private async Task ReadConfig(string file) { IConfigSerializer serializer = GetSerializerForFile(file); - string text = await File.ReadAllTextAsync(file); + string text = await File.ReadAllTextAsync(file).ConfigureAwait(false); return serializer.Deserialize(text); } diff --git a/src/PluginMerge/Configuration/Serialization/JsonConfigSerializer.cs b/src/PluginMerge/Configuration/Serialization/JsonConfigSerializer.cs index fe71526..aec3118 100644 --- a/src/PluginMerge/Configuration/Serialization/JsonConfigSerializer.cs +++ b/src/PluginMerge/Configuration/Serialization/JsonConfigSerializer.cs @@ -7,10 +7,7 @@ public class JsonConfigSerializer : IConfigSerializer public static readonly JsonConfigSerializer Instance = new(); private static readonly JsonSerializerOptions Options = new() { WriteIndented = true }; - private JsonConfigSerializer() - { - - } + private JsonConfigSerializer() { } public string Serialize(T data) { diff --git a/src/PluginMerge/Configuration/Serialization/YamlConfigSerializer.cs b/src/PluginMerge/Configuration/Serialization/YamlConfigSerializer.cs index 50ba8f4..5e6385e 100644 --- a/src/PluginMerge/Configuration/Serialization/YamlConfigSerializer.cs +++ b/src/PluginMerge/Configuration/Serialization/YamlConfigSerializer.cs @@ -6,12 +6,9 @@ public class YamlConfigSerializer : IConfigSerializer { public static readonly YamlConfigSerializer Instance = new (); private static readonly ISerializer Serializer = new SerializerBuilder().Build(); - private static readonly IDeserializer Deserializer = new DeserializerBuilder().Build(); + private static readonly IDeserializer Deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build(); - private YamlConfigSerializer() - { - - } + private YamlConfigSerializer() { } public string Serialize(T data) { diff --git a/src/PluginMerge/Constants.cs b/src/PluginMerge/Constants.cs index bc75a1c..427c293 100644 --- a/src/PluginMerge/Constants.cs +++ b/src/PluginMerge/Constants.cs @@ -20,7 +20,12 @@ public static class Definitions /// /// Files with this comment will be ignored /// - public const string ExcludeFile = "//Define:ExcludeFile"; + public const string ExcludeFile = "//Define:ExcludeFile"; + + /// + /// Files with this comment will be added as extension methods + /// + public const string ExtensionFile = "//Define:ExtensionMethods"; /// /// Files with this comment will be ordered based on the tag @@ -36,12 +41,12 @@ public static class Regexs /// /// Matches the Info tag /// - public static readonly Regex Info = new(@"\s*\[Info\(\s*""(?.*)""\s*,\s*""(?<Author>.*)""\s*,\s*""(?<Version>.*)""\s*\)\]$", RegexOptions.Compiled); + public static readonly Regex Info = new(@"\s*//\[Info\(\s*""(?<Title>.*)""\s*,\s*""(?<Author>.*)""\s*,\s*""(?<Version>.*)""\s*\)\]$", RegexOptions.Compiled); /// <summary> /// Matches the description tag /// </summary> - public static readonly Regex Description = new(@"\s*\[Description\(\s*""(?<Description>.*)""\s*\)\]$", RegexOptions.Compiled); + public static readonly Regex Description = new(@"\s*//\[Description\(\s*""(?<Description>.*)""\s*\)\]$", RegexOptions.Compiled); } public static class CloseCodes @@ -53,5 +58,7 @@ public static class CloseCodes public const int MergeConfigError = 2002; public const int MergeFilesError = 2003; public const int CompileFilesError = 2004; + public const int RenameFileNotFound = 3001; + public const int RenameFileContainsInvalidSource = 3002; } } \ No newline at end of file diff --git a/src/PluginMerge/Creator/BaseCreator.cs b/src/PluginMerge/Creator/BaseCreator.cs deleted file mode 100644 index 68f45bd..0000000 --- a/src/PluginMerge/Creator/BaseCreator.cs +++ /dev/null @@ -1,258 +0,0 @@ -namespace PluginMerge.Creator; - -/// <summary> -/// -/// </summary> -public class BaseCreator -{ - private readonly List<FileHandler> _files; - private readonly PluginMergeConfig _settings; - - private FileHandler _plugin; - /// <summary> - /// Files that are part of the plugin - /// </summary> - protected readonly List<FileHandler> PluginFiles = new(); - - /// <summary> - /// Files that contain data types - /// </summary> - protected readonly List<FileHandler> DataFiles = new(); - - /// <summary> - /// Files that contain frameworks - /// </summary> - private readonly List<FileHandler> _frameworks = new(); - - /// <summary> - /// Code writer for the final file - /// </summary> - protected CodeWriter Writer; - - protected readonly ILogger Logger; - - /// <summary> - /// Constructor for the base creator - /// </summary> - /// <param name="settings">Creator settings</param> - /// <param name="files">Files that were processed</param> - protected BaseCreator(PluginMergeConfig settings, List<FileHandler> files) - { - Logger = this.GetLogger(); - _files = files; - _settings = settings; - } - - /// <summary> - /// Creates a PluginCreator or Framework created based on the mode - /// </summary> - /// <param name="settings">Plugin settings for the creator</param> - /// <param name="files">Files to be processed</param> - /// <returns></returns> - public static BaseCreator CreateForMode(PluginMergeConfig settings, List<FileHandler> files) - { - return settings.Merge.CreatorMode switch - { - CreatorMode.Plugin => new PluginCreator(settings, files), - CreatorMode.Framework => new FrameworkCreator(settings, files), - CreatorMode.MergeFramework => new MergeFrameworkCreator(settings, files), - _ => null - }; - } - - /// <summary> - /// Creates the CodeWriter for the plugin - /// </summary> - public virtual bool Create() - { - _plugin = FindPlugin(); - if (_plugin == null) - { - Logger.LogError("No Plugin Found in Files"); - return false; - } - - FilterFiles(_plugin.PluginData); - - Writer = new CodeWriter(_plugin, _settings.Merge.CodeStyle, _settings.Merge.PluginName); - return true; - } - - /// <summary> - /// Returns the file that has plugin declarations - /// </summary> - /// <returns></returns> - private FileHandler FindPlugin() - { - return _files.FirstOrDefault(f => f.Type.HasFlag(FileSettings.Plugin)); - } - - /// <summary> - /// Filters the files into their given types - /// </summary> - private void FilterFiles(PluginData data) - { - foreach (FileHandler file in _files) - { - if (file.Type.HasFlag(FileSettings.Framework)) - { - _frameworks.Add(file); - foreach (FileType type in file.FileTypes) - { - if (type.TypeNamespace == data.NameSpace && type.TypeName == data.ClassName) - { - type.IsPluginType = true; - } - } - continue; - } - - bool isPluginType = false; - foreach (FileType type in file.FileTypes) - { - if (type.TypeNamespace == data.NameSpace && type.TypeName == data.ClassName) - { - PluginFiles.Add(file); - type.IsPluginType = true; - isPluginType = true; - } - } - - if (isPluginType) - { - continue; - } - - DataFiles.Add(file); - } - } - - /// <summary> - /// writes usings to the code writer - /// </summary> - protected void WriteReferences() - { - Writer.WriteReferences(_settings.Merge.References); - } - - /// <summary> - /// writes usings to the code writer - /// </summary> - protected void WriteDefines() - { - Writer.WriteDefines(_files - .SelectMany(f => f.DefineDirectives) - .Concat(_settings.Merge.Defines) - .Distinct()); - } - - /// <summary> - /// writes usings to the code writer - /// </summary> - protected void WriteUsings() - { - Writer.WriteUsings(_files - .SelectMany(f => f.UsingStatements) - .Distinct() - .Where(u => !_settings.Merge.IgnoreNameSpaces.Any(u.StartsWith))); - - Writer.WriteUsings(_files - .SelectMany(f => f.UsingAliases) - .Distinct()); - } - - /// <summary> - /// Writes namespace to the code writer - /// </summary> - protected void WriteNamespace() - { - Writer.WriteComment($"{_settings.Merge.PluginName} created with PluginMerge v({typeof(Program).Assembly.GetName().Version}) by MJSU @ https://github.com/dassjosh/Plugin.Merge"); - Writer.WriteNamespace(_settings.PlatformSettings.Namespace); - Writer.WriteLine(); - Writer.WriteStartBracket(); - } - - /// <summary> - /// Writes the start of the plugin class - /// </summary> - /// <param name="isPartial"></param> - /// <param name="isFramework"></param> - protected void StartPluginClass(bool isPartial, bool isFramework) - { - Writer.WriteInfoAttribute(isFramework); - Writer.WriteDescriptionAttribute(isFramework); - Writer.WriteStartClass(_settings.Merge.PluginName, _settings.Merge.BaseClass, isPartial); - Writer.WriteStartBracket(); - } - - /// <summary> - /// Writes the end of the plugin class - /// </summary> - protected void EndPluginClass() - { - Writer.WriteEndBracket(); - Writer.WriteLine(); - } - - /// <summary> - /// Writes the end of the namespace - /// </summary> - protected void EndNamespace() - { - Writer.WriteEndBracket(); - } - - /// <summary> - /// Writes the file to the plugin - /// </summary> - /// <param name="file"></param> - protected void Write(FileHandler file) - { - if (file.FileTypes.All(f => !f.HasCode())) - { - return; - } - - Writer.WriteStartRegion(file.RegionName); - foreach (FileType type in file.FileTypes.Where(type => type.HasCode())) - { - type.Write(Writer); - } - - Writer.WriteEndRegion(); - Writer.WriteLine(); - } - - /// <summary> - /// Writes the framework to the plugin - /// </summary> - protected void WriteFrameworks() - { - foreach (FileHandler file in _frameworks) - { - Logger.LogDebug("Writing framework file: {File}", file.FilePath); - Writer.WriteComment($"Framework {file.PluginData?.Title} v({file.PluginData?.Version}) by {file.PluginData?.Author}"); - Writer.WriteComment(file.PluginData?.Description); - Writer.WriteStartRegion($"Merged Framework {file.PluginData?.Title}"); - Writer.WriteStartClass(_settings.Merge.PluginName, null, true); - Writer.WriteStartBracket(); - foreach (FileType type in file.FileTypes.Where(f => f.HasCode())) - { - type.Write(Writer); - } - - Writer.WriteEndBracket(); - Writer.WriteEndRegion(); - Writer.WriteLine(); - } - } - - /// <summary> - /// Returns the final output code - /// </summary> - /// <returns></returns> - public string ToCode() - { - return Writer.GetCode(); - } -} \ No newline at end of file diff --git a/src/PluginMerge/Creator/FileCreator.cs b/src/PluginMerge/Creator/FileCreator.cs new file mode 100644 index 0000000..8db5f87 --- /dev/null +++ b/src/PluginMerge/Creator/FileCreator.cs @@ -0,0 +1,372 @@ +namespace PluginMerge.Creator; + +/// <summary> +/// +/// </summary> +public class FileCreator +{ + private readonly List<FileHandler> _files; + private readonly PluginMergeConfig _settings; + + private FileHandler _plugin; + /// <summary> + /// Files that are part of the plugin + /// </summary> + private readonly List<FileHandler> _pluginFiles = new(); + + /// <summary> + /// Files that contain data types + /// </summary> + private readonly List<FileHandler> _dataFiles = new(); + + /// <summary> + /// Files that contain extension methods + /// </summary> + private readonly List<FileType> _extensionTypes = new(); + + /// <summary> + /// Files that contain frameworks + /// </summary> + private readonly List<FileHandler> _frameworks = new(); + + private readonly bool IsPluginMode; + private readonly bool IsFrameworkMode; + private readonly bool IsMergeFrameworkMode; + + /// <summary> + /// Code writer for the final file + /// </summary> + private CodeWriter _writer; + + private readonly ILogger _logger; + + /// <summary> + /// Constructor for the base creator + /// </summary> + /// <param name="settings">Creator settings</param> + /// <param name="files">Files that were processed</param> + public FileCreator(PluginMergeConfig settings, List<FileHandler> files) + { + _logger = LogBuilder.GetLogger<FileCreator>(); + _files = files; + _settings = settings; + IsPluginMode = settings.Merge.CreatorMode is CreatorMode.Plugin; + IsFrameworkMode = settings.Merge.CreatorMode is CreatorMode.Framework; + IsMergeFrameworkMode = settings.Merge.CreatorMode is CreatorMode.MergeFramework; + } + + /// <summary> + /// Creates the CodeWriter for the plugin + /// </summary> + public bool Create() + { + _plugin = FindPlugin(); + if (_plugin?.PluginData is null) + { + _logger.LogError("No Plugin Found in Files"); + return false; + } + + FilterFiles(_plugin.PluginData); + + _writer = new CodeWriter(_plugin.PluginData, _settings.Merge); + + WriteReferences(); + WriteDefines(); + WriteUsings(); + WriteNamespace(); + if (IsMergeFrameworkMode) _writer.WriteFramework(); + StartPluginClass(); + WritePluginFiles(); + if (IsPluginMode || IsFrameworkMode) WriteDataFiles(); + EndPluginClass(); + if (IsMergeFrameworkMode) WriteDataFiles(); + WriteFrameworks(); + EndNamespace(); + WriteExtensionMethods(); + return true; + } + + /// <summary> + /// Returns the file that has plugin declarations + /// </summary> + /// <returns></returns> + private FileHandler FindPlugin() + { + return _files.FirstOrDefault(f => f.IsPluginFile()); + } + + /// <summary> + /// Filters the files into their given types + /// </summary> + private void FilterFiles(PluginData data) + { + foreach (FileHandler file in _files) + { + bool isPluginType = false; + foreach (FileType type in file.FileTypes) + { + if (type.IsExtensionMethods()) + { + _extensionTypes.Add(type); + continue; + } + + if (type.TypeNamespace == data.NameSpace && type.TypeName == data.ClassName) + { + type.AddSettings(FileSettings.Plugin); + isPluginType = true; + } + } + + if (file.IsFrameworkFile()) + { + _frameworks.Add(file); + } + else if (isPluginType) + { + _pluginFiles.Add(file); + } + else + { + _dataFiles.Add(file); + } + } + } + + /// <summary> + /// writes usings to the code writer + /// </summary> + private void WriteReferences() + { + _writer.WriteReferences(_settings.Merge.References); + } + + /// <summary> + /// writes usings to the code writer + /// </summary> + private void WriteDefines() + { + _writer.WriteDefines(_files + .SelectMany(f => f.DefineDirectives) + .Concat(_settings.Merge.Defines) + .Distinct()); + } + + /// <summary> + /// writes usings to the code writer + /// </summary> + private void WriteUsings() + { + List<string> extensionNameSpaces = _files + .SelectMany(f => f.FileTypes + .Where(f => f.IsExtensionMethods()) + .Select(f => f.TypeNamespace)) + .ToList(); + + _writer.WriteUsings(_files + .SelectMany(f => f.UsingStatements) + .Distinct() + .Where(u => !_settings.Merge.IgnoreNameSpaces.Any(u.StartsWith) && !extensionNameSpaces.Contains(u))); + + _writer.WriteUsings(_files + .SelectMany(f => f.UsingAliases) + .Distinct()); + + if (_extensionTypes.Count != 0) + { + _writer.WriteUsing(GetExtensionNamespace()); + } + } + + /// <summary> + /// Writes namespace to the code writer + /// </summary> + private void WriteNamespace() + { + _writer.WriteComment($"{_settings.Merge.PluginName} created with PluginMerge v({typeof(Program).Assembly.GetName().Version}) by MJSU @ https://github.com/dassjosh/Plugin.Merge"); + _writer.WriteNamespace(_settings.PlatformSettings.Namespace); + _writer.WriteLine(); + _writer.WriteStartBracket(); + } + + /// <summary> + /// Writes namespace to the code writer + /// </summary> + private void WriteExtensionNamespace() + { + _writer.WriteLine(); + _writer.WriteNamespace(GetExtensionNamespace()); + _writer.WriteLine(); + _writer.WriteStartBracket(); + if (_settings.Merge.CreatorMode != CreatorMode.MergeFramework) + { + WriteExtensionUsings(); + } + _writer.WriteLine(); + } + + private void WriteExtensionUsings() + { + List<string> allTypes = new(); + foreach (FileHandler file in _files) + { + foreach (FileType type in file.FileTypes) + { + if (!type.IsExtensionMethods() && !allTypes.Contains(type.TypeName) && _settings.Merge.PluginName != type.TypeName) + { + allTypes.Add(type.TypeName); + } + } + } + + HashSet<string> addedTypes = new(); + foreach (FileType file in _extensionTypes) + { + foreach (string type in allTypes) + { + if (file.ContainsType(type) && addedTypes.Add(type)) + { + _writer.WriteIndent(); + _writer.WriteUsingAlias(type, $"{_settings.Merge.PluginName}.{type}"); + } + } + } + } + + /// <summary> + /// Writes the start of the plugin class + /// </summary> + private void StartPluginClass() + { + bool isFramework = IsFrameworkMode || IsMergeFrameworkMode; + _writer.WriteInfoAttribute(isFramework); + _writer.WriteDescriptionAttribute(isFramework); + _writer.WriteStartClass(_settings.Merge.PluginName, _plugin.PluginData.PluginBaseTypes, true); + _writer.WriteStartBracket(); + } + + /// <summary> + /// Writes the end of the plugin class + /// </summary> + private void EndPluginClass() + { + _writer.WriteEndBracket(); + _writer.WriteLine(); + } + + /// <summary> + /// Writes the end of the namespace + /// </summary> + private void EndNamespace() + { + _writer.WriteEndBracket(); + } + + /// <summary> + /// Writes the file to the plugin + /// </summary> + /// <param name="file"></param> + private void Write(FileHandler file) + { + if (file.FileTypes.All(f => !f.HasCode())) + { + return; + } + + if (file.FileTypes.All(f => f.IsExtensionMethods())) + { + return; + } + + _writer.WriteStartRegion(file.RegionName); + foreach (FileType type in file.FileTypes.Where(type => type.HasCode() && !type.IsExtensionMethods())) + { + type.Write(_writer); + } + + _writer.WriteEndRegion(); + _writer.WriteLine(); + } + + private void WritePluginFiles() + { + foreach (FileHandler file in _pluginFiles) + { + _logger.LogDebug("Writing plugin file: {Path}", file.FilePath); + Write(file); + } + } + + private void WriteDataFiles() + { + foreach (FileHandler file in _dataFiles) + { + _logger.LogDebug("Writing data file: {Path}", file.FilePath); + Write(file); + } + } + + /// <summary> + /// Writes the framework to the plugin + /// </summary> + private void WriteFrameworks() + { + foreach (FileHandler file in _frameworks) + { + _logger.LogDebug("Writing framework file: {File}", file.FilePath); + _writer.WriteComment($"Framework {file.PluginData?.Title} v({file.PluginData?.Version}) by {file.PluginData?.Author}"); + _writer.WriteComment(file.PluginData?.Description); + _writer.WriteStartRegion($"Merged Framework {file.PluginData?.Title}"); + _writer.WriteStartClass(_settings.Merge.PluginName, null, true); + _writer.WriteStartBracket(); + foreach (FileType type in file.FileTypes.Where(f => f.HasCode() && !f.IsExtensionMethods())) + { + type.Write(_writer); + } + + _writer.WriteEndBracket(); + _writer.WriteEndRegion(); + _writer.WriteLine(); + } + } + + private void WriteExtensionMethods() + { + if (_extensionTypes.Count == 0) + { + return; + } + + bool isFramework = IsFrameworkMode || IsMergeFrameworkMode; + + WriteExtensionNamespace(); + foreach (FileType type in _extensionTypes) + { + _logger.LogDebug("Writing extension type: {Path}", type.TypeName); + + if (isFramework) + { + _writer.WriteLine(); + _writer.WriteDefinition(Constants.Definitions.ExtensionFile); + } + + type.Write(_writer); + } + EndNamespace(); + } + + /// <summary> + /// Returns the final output code + /// </summary> + /// <returns></returns> + public string ToCode() + { + return _writer.GetCode(); + } + + private string GetExtensionNamespace() + { + return $"{_settings.PlatformSettings.Namespace}.{_settings.Merge.PluginName}Extensions"; + } +} \ No newline at end of file diff --git a/src/PluginMerge/Creator/FrameworkCreator.cs b/src/PluginMerge/Creator/FrameworkCreator.cs deleted file mode 100644 index 489cbaa..0000000 --- a/src/PluginMerge/Creator/FrameworkCreator.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace PluginMerge.Creator; - -/// <summary> -/// Represents a creator for frameworks -/// </summary> -public class FrameworkCreator : BaseCreator -{ - /// <summary> - /// Framework Creator Constructor - /// </summary> - /// <param name="settings"></param> - /// <param name="files"></param> - public FrameworkCreator(PluginMergeConfig settings, List<FileHandler> files) : base(settings, files) - { - } - - /// <summary> - /// Creates a framework plugin - /// </summary> - public override bool Create() - { - if (!base.Create()) - { - return false; - } - - WriteReferences(); - WriteDefines(); - WriteUsings(); - WriteNamespace(); - StartPluginClass(true, true); - - foreach (FileHandler file in PluginFiles) - { - Logger.LogDebug("Writing plugin file: {Path}", file.FilePath); - Write(file); - } - - foreach (FileHandler file in DataFiles) - { - Logger.LogDebug("Writing data file: {Path}", file.FilePath); - Write(file); - } - - EndPluginClass(); - WriteFrameworks(); - EndNamespace(); - return true; - } -} \ No newline at end of file diff --git a/src/PluginMerge/Creator/MergeFrameworkCreator.cs b/src/PluginMerge/Creator/MergeFrameworkCreator.cs deleted file mode 100644 index b923694..0000000 --- a/src/PluginMerge/Creator/MergeFrameworkCreator.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace PluginMerge.Creator; - -/// <summary> -/// Represents a creator for frameworks -/// </summary> -public class MergeFrameworkCreator : BaseCreator -{ - /// <summary> - /// Framework Creator Constructor - /// </summary> - /// <param name="settings"></param> - /// <param name="files"></param> - public MergeFrameworkCreator(PluginMergeConfig settings, List<FileHandler> files) : base(settings, files) - { - } - - /// <summary> - /// Creates a framework plugin - /// </summary> - public override bool Create() - { - if (!base.Create()) - { - return false; - } - - WriteReferences(); - WriteDefines(); - WriteUsings(); - WriteNamespace(); - Writer.WriteFramework(); - StartPluginClass(true, true); - - foreach (FileHandler file in PluginFiles) - { - Logger.LogDebug("Writing plugin file: {Path}", file.FilePath); - Write(file); - } - - EndPluginClass(); - - foreach (FileHandler file in DataFiles) - { - Logger.LogDebug("Writing data file: {Path}", file.FilePath); - Write(file); - } - - WriteFrameworks(); - EndNamespace(); - return true; - } -} \ No newline at end of file diff --git a/src/PluginMerge/Creator/PluginCreator.cs b/src/PluginMerge/Creator/PluginCreator.cs deleted file mode 100644 index 7836043..0000000 --- a/src/PluginMerge/Creator/PluginCreator.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace PluginMerge.Creator; - -/// <summary> -/// Represents a plugin creator -/// </summary> -public class PluginCreator : BaseCreator -{ - /// <summary> - /// Plugin Creator Constructor - /// </summary> - /// <param name="settings"></param> - /// <param name="files"></param> - public PluginCreator(PluginMergeConfig settings, List<FileHandler> files) : base(settings, files) - { - } - - /// <summary> - /// Creates a plugin - /// </summary> - public override bool Create() - { - if (!base.Create()) - { - return false; - } - - WriteReferences(); - WriteDefines(); - WriteUsings(); - WriteNamespace(); - StartPluginClass(true, false); - - foreach (FileHandler file in PluginFiles) - { - Logger.LogDebug("Writing plugin file: {Path}", file.FilePath); - Write(file); - } - - foreach (FileHandler file in DataFiles) - { - Logger.LogDebug("Writing data file: {Path}", file.FilePath); - Write(file); - } - - EndPluginClass(); - WriteFrameworks(); - EndNamespace(); - return true; - } -} \ No newline at end of file diff --git a/src/PluginMerge/Enums/FileSettings.cs b/src/PluginMerge/Enums/FileSettings.cs index 296a0fc..19483ac 100644 --- a/src/PluginMerge/Enums/FileSettings.cs +++ b/src/PluginMerge/Enums/FileSettings.cs @@ -6,6 +6,11 @@ namespace PluginMerge.Enums; [Flags] public enum FileSettings { + /// <summary> + /// Represents no file settings + /// </summary> + None = 0, + /// <summary> /// File inherits from Plugin class /// </summary> @@ -19,5 +24,10 @@ public enum FileSettings /// <summary> /// File wants to be excluded /// </summary> - Exclude = 1 << 2 + Exclude = 1 << 2, + + /// <summary> + /// File contains extension methods + /// </summary> + Extension = 1 << 3 } \ No newline at end of file diff --git a/src/PluginMerge/Enums/Platform.cs b/src/PluginMerge/Enums/Platform.cs index a01d0af..168ea7a 100644 --- a/src/PluginMerge/Enums/Platform.cs +++ b/src/PluginMerge/Enums/Platform.cs @@ -13,5 +13,6 @@ public enum Platform /// <summary> /// uMod platform /// </summary> + // ReSharper disable once InconsistentNaming uMod } \ No newline at end of file diff --git a/src/PluginMerge/Extensions/TextWriterExt.cs b/src/PluginMerge/Extensions/TextWriterExt.cs index dcc1267..d7c086b 100644 --- a/src/PluginMerge/Extensions/TextWriterExt.cs +++ b/src/PluginMerge/Extensions/TextWriterExt.cs @@ -11,11 +11,11 @@ public static void WriteStartColor(this TextWriter textWriter, ConsoleColor? bac string backgroundColor = background.HasValue ? GetBackgroundColorEscapeCode(background.Value) : null; string foregroundColor = foreground.HasValue ? GetForegroundColorEscapeCode(foreground.Value) : null; - if (backgroundColor != null) + if (backgroundColor is not null) { textWriter.Write(backgroundColor); } - if (foregroundColor != null) + if (foregroundColor is not null) { textWriter.Write(foregroundColor); } diff --git a/src/PluginMerge/GlobalUsings.cs b/src/PluginMerge/GlobalUsings.cs index 912d26d..59a0c33 100644 --- a/src/PluginMerge/GlobalUsings.cs +++ b/src/PluginMerge/GlobalUsings.cs @@ -10,4 +10,5 @@ global using PluginMerge.Extensions; global using PluginMerge.Logging; global using PluginMerge.Merge; -global using PluginMerge.Scanner; \ No newline at end of file +global using PluginMerge.Scanner; +global using PluginMerge.Writer; \ No newline at end of file diff --git a/src/PluginMerge/Logging/LogHandler.cs b/src/PluginMerge/Logging/LogBuilder.cs similarity index 73% rename from src/PluginMerge/Logging/LogHandler.cs rename to src/PluginMerge/Logging/LogBuilder.cs index cc44daf..82e1411 100644 --- a/src/PluginMerge/Logging/LogHandler.cs +++ b/src/PluginMerge/Logging/LogBuilder.cs @@ -2,7 +2,7 @@ namespace PluginMerge.Logging; -public static class LogHandler +public static class LogBuilder { private static readonly ConcurrentDictionary<Type, ILogger> Loggers = new(); private static ILoggerFactory _factory; @@ -23,15 +23,15 @@ public static void InitLogger(LogLevel logLevel) }); } - public static ILogger GetLogger(this object entity) + public static ILogger<T> GetLogger<T>() { - if (Loggers.TryGetValue(entity.GetType(), out ILogger logger)) + if (Loggers.TryGetValue(typeof(T), out ILogger cachedLogger)) { - return logger; + return (ILogger<T>)cachedLogger; } - logger = _factory.CreateLogger(entity.GetType()); - Loggers[entity.GetType()] = logger; + ILogger<T> logger = _factory.CreateLogger<T>(); + Loggers[typeof(T)] = logger; return logger; } } \ No newline at end of file diff --git a/src/PluginMerge/Logging/LogFormatter.cs b/src/PluginMerge/Logging/LogFormatter.cs index 005f774..a985fea 100644 --- a/src/PluginMerge/Logging/LogFormatter.cs +++ b/src/PluginMerge/Logging/LogFormatter.cs @@ -53,7 +53,7 @@ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeP WriteMessage(textWriter, message); Exception exception = logEntry.Exception; - if (exception != null) + if (exception is not null) { WriteMessage(textWriter, exception.ToString()); } @@ -81,6 +81,7 @@ private static string GetLogLevelString(LogLevel logLevel) LogLevel.Warning => "[warn] ", LogLevel.Error => "[fail] ", LogLevel.Critical => "[crit] ", + LogLevel.None => string.Empty, _ => throw new ArgumentOutOfRangeException(nameof(logLevel)) }; } diff --git a/src/PluginMerge/Merge/FileHandler.cs b/src/PluginMerge/Merge/FileHandler.cs index 0bda4ae..e59064a 100644 --- a/src/PluginMerge/Merge/FileHandler.cs +++ b/src/PluginMerge/Merge/FileHandler.cs @@ -2,7 +2,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using PluginMerge.Scanner; namespace PluginMerge.Merge; @@ -19,7 +18,7 @@ public class FileHandler /// <summary> /// Type flags for the code inside the file /// </summary> - public FileSettings Type { get; private set; } + private FileSettings Settings { get; set; } /// <summary> /// Data related to a plugin @@ -49,7 +48,7 @@ public class FileHandler /// <summary> /// The order the file requested to be in /// </summary> - public int Order { get; private set; } = 100; + public int Order { get; private set; } = 1000; /// <summary> /// The name of the region for the file @@ -57,7 +56,7 @@ public class FileHandler public string RegionName { get; } private readonly ILogger _logger; - private string _text; + private string _sourceCode; /// <summary> /// Constructor for FileHandler. @@ -66,7 +65,7 @@ public class FileHandler /// <param name="file"></param> public FileHandler(ScannedFile file) { - _logger = this.GetLogger(); + _logger = LogBuilder.GetLogger<FileHandler>(); FilePath = file.FileName; RegionName = FilePath.Replace(file.InputPath, "").TrimStart(Path.DirectorySeparatorChar); } @@ -75,18 +74,25 @@ public FileHandler(ScannedFile file) /// Processes the code inside the file. /// </summary> /// <param name="settings">How the file should be read.</param> - public async Task ProcessFile(PlatformSettings settings) + /// <param name="options">Parsing options</param> + public async Task ProcessFile(PlatformSettings settings, CSharpParseOptions options) { _logger.LogDebug("Start processing file at path: {Path}", FilePath); - _text = await File.ReadAllTextAsync(FilePath); - SyntaxTree tree = CSharpSyntaxTree.ParseText(_text, new CSharpParseOptions(settings.Lang)); + _sourceCode = await File.ReadAllTextAsync(FilePath).ConfigureAwait(false); + SyntaxTree tree = CSharpSyntaxTree.ParseText(_sourceCode, options); - if (await tree.GetRootAsync() is not CompilationUnitSyntax root) + if (await tree.GetRootAsync().ConfigureAwait(false) is not CompilationUnitSyntax root) { return; } - await Task.WhenAll(ProcessComments(root), ProcessUsings(settings, root), ProcessNamespace(root)); + await ProcessComments(root).ConfigureAwait(false); + if (IsExcludedFile()) + { + return; + } + + await Task.WhenAll(ProcessUsings(settings, root), ProcessNamespace(root)).ConfigureAwait(false); } private Task ProcessComments(CompilationUnitSyntax root) @@ -95,38 +101,37 @@ private Task ProcessComments(CompilationUnitSyntax root) foreach (SyntaxTrivia trivia in root.DescendantTrivia()) { - SyntaxKind kind = trivia.Kind(); - if (kind == SyntaxKind.SingleLineCommentTrivia) + if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) { if (trivia.Token.Parent is not (NamespaceDeclarationSyntax or ClassDeclarationSyntax or AttributeListSyntax)) { continue; } - - string commentValue = trivia.ToString(); - switch (commentValue) - { - case Constants.Definitions.Framework: - Type |= FileSettings.Framework; - break; - case Constants.Definitions.ExcludeFile: - Type |= FileSettings.Exclude; - break; - } - if (commentValue.Contains(Constants.Definitions.OrderFile)) + string comment = trivia.ToString(); + if (comment == Constants.Definitions.Framework) + { + Settings |= FileSettings.Framework; + } + else if (comment == Constants.Definitions.ExcludeFile) { - if (int.TryParse(commentValue.Replace(Constants.Definitions.OrderFile, string.Empty), out int order)) + Settings |= FileSettings.Exclude; + return Task.CompletedTask; + } + else if (comment.Contains(Constants.Definitions.OrderFile)) + { + if (int.TryParse(comment.Replace(Constants.Definitions.OrderFile, string.Empty), out int order)) { Order = order; } } - ProcessFrameworkComments(commentValue); + ProcessFrameworkComments(comment); } - else if (kind == SyntaxKind.DefineDirectiveTrivia) + else if (trivia.IsKind(SyntaxKind.DefineDirectiveTrivia)) { - DefineDirectives.Add(trivia.ToString().Replace("#define ", string.Empty)); + const string define = "#define "; + DefineDirectives.Add(trivia.ToString().Substring(define.Length)); } } @@ -135,18 +140,23 @@ private Task ProcessComments(CompilationUnitSyntax root) private void ProcessFrameworkComments(string comment) { - Match match = Constants.Regexs.Info.Match(comment); - if (match.Success) + if (comment.StartsWith("//[Info")) { - PluginData ??= new PluginData(); - PluginData.SetInfo(match.Groups["Title"].Value, match.Groups["Author"].Value, match.Groups["Version"].Value); + Match match = Constants.Regexs.Info.Match(comment); + if (match.Success) + { + PluginData ??= new PluginData(); + PluginData.SetInfo(match.Groups["Title"].Value, match.Groups["Author"].Value, match.Groups["Version"].Value); + } } - - match = Constants.Regexs.Description.Match(comment); - if (match.Success) + else if (comment.StartsWith("//[Description(")) { - PluginData ??= new PluginData(); - PluginData.SetDescription(match.Groups["Description"].Value); + Match match = Constants.Regexs.Description.Match(comment); + if (match.Success) + { + PluginData ??= new PluginData(); + PluginData.SetDescription(match.Groups["Description"].Value); + } } } @@ -158,7 +168,7 @@ private Task ProcessUsings(PlatformSettings settings, CompilationUnitSyntax root string name = @using.Name.ToString(); if (!name.Equals(settings.Namespace)) { - if (@using.Alias == null) + if (@using.Alias is null) { UsingStatements.Add(name); } @@ -179,17 +189,30 @@ private Task ProcessNamespace(CompilationUnitSyntax root) { foreach (BaseTypeDeclarationSyntax node in @namespace.ChildNodes().OfType<BaseTypeDeclarationSyntax>()) { - FileType data = new(_text, node, @namespace.Name.ToString()); + FileSettings typeSettings = Settings; + foreach (SyntaxTrivia trivia in node.DescendantTrivia()) + { + if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) + { + if (trivia.ToString() == Constants.Definitions.ExtensionFile) + { + typeSettings |= FileSettings.Extension; + } + } + } + + FileType data = new(_sourceCode, node, @namespace.Name.ToString(), typeSettings); FileTypes.Add(data); - if (node.BaseList != null && node.BaseList.Types.Any(type => type.Type.ToString().EndsWith("Plugin"))) + if (!IsFrameworkFile() && node.BaseList is not null && node.BaseList.Types.Any(type => type.Type.ToString().EndsWith("Plugin"))) { - Type |= FileSettings.Plugin; + Settings |= FileSettings.Plugin; PluginData = new PluginData(); PluginData.SetPluginType(data.TypeNamespace, data.TypeName); + PluginData.SetBaseTypes(node.BaseList.Types); } - if (PluginData != null) + if (PluginData is not null) { ProcessAttributes(node); } @@ -205,11 +228,11 @@ private void ProcessAttributes(BaseTypeDeclarationSyntax node) { foreach (AttributeSyntax attribute in list.Attributes) { - if (attribute.ArgumentList == null) + if (attribute.ArgumentList is null) { continue; } - + string name = attribute.Name.ToString(); if (name == "Info" && attribute.ArgumentList.Arguments.Count >= 3) { @@ -224,4 +247,19 @@ private void ProcessAttributes(BaseTypeDeclarationSyntax node) } } } + + public bool IsPluginFile() + { + return Settings.HasFlag(FileSettings.Plugin); + } + + public bool IsFrameworkFile() + { + return Settings.HasFlag(FileSettings.Framework); + } + + public bool IsExcludedFile() + { + return Settings.HasFlag(FileSettings.Exclude); + } } \ No newline at end of file diff --git a/src/PluginMerge/Merge/FileType.cs b/src/PluginMerge/Merge/FileType.cs index bce41a8..f2ce556 100644 --- a/src/PluginMerge/Merge/FileType.cs +++ b/src/PluginMerge/Merge/FileType.cs @@ -8,11 +8,9 @@ namespace PluginMerge.Merge; public class FileType { public readonly string TypeName; - public readonly string TypeNamespace; + private FileSettings _settings; - public bool IsPluginType; - private readonly string _sourceCode; private readonly BaseTypeDeclarationSyntax _type; @@ -22,12 +20,14 @@ public class FileType /// <param name="source"></param> /// <param name="type"></param> /// <param name="typeNamespace"></param> - public FileType(string source, BaseTypeDeclarationSyntax type, string typeNamespace) + /// <param name="settings">Settings for the type</param> + public FileType(string source, BaseTypeDeclarationSyntax type, string typeNamespace, FileSettings settings) { _sourceCode = source; _type = type; TypeName = type.Identifier.ToString(); TypeNamespace = typeNamespace; + _settings = settings; } /// <summary> @@ -52,7 +52,7 @@ private ReadOnlySpan<char> GetCode() int endIndex = _type.Span.End; //If plugin type we want code between the open and close braces - if (IsPluginType) + if (IsPlugin()) { startIndex = _type.OpenBraceToken.SpanStart + 1; endIndex = _type.CloseBraceToken.SpanStart - 1; @@ -65,4 +65,29 @@ public bool HasCode() { return !GetCode().IsEmpty; } + + public bool ContainsType(string type) + { + return _sourceCode.AsSpan().Slice(_type.SpanStart, _type.Span.Length).Contains(type.AsSpan(), StringComparison.CurrentCulture); + } + + public void AddSettings(FileSettings settings) + { + _settings |= settings; + } + + public bool IsPlugin() + { + return _settings.HasFlag(FileSettings.Plugin); + } + + public bool IsFramework() + { + return _settings.HasFlag(FileSettings.Framework); + } + + public bool IsExtensionMethods() + { + return _settings.HasFlag(FileSettings.Extension); + } } \ No newline at end of file diff --git a/src/PluginMerge/Merge/MergeHandler.cs b/src/PluginMerge/Merge/MergeHandler.cs index fc47b8b..0fb2cf6 100644 --- a/src/PluginMerge/Merge/MergeHandler.cs +++ b/src/PluginMerge/Merge/MergeHandler.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp; namespace PluginMerge.Merge; @@ -13,18 +14,18 @@ public MergeHandler(PluginMergeConfig config) { _config = config; _merge = config.Merge; - _logger = this.GetLogger(); + _logger = LogBuilder.GetLogger<MergeHandler>(); } public async Task Run() { Stopwatch sw = Stopwatch.StartNew(); - _logger.LogInformation("Starting Plugin Merge Mode: {Mode}", _merge.CreatorMode); + _logger.LogInformation("Starting Plugin Merge Version: {Version} Mode: {Mode}", typeof(Program).Assembly.GetName().Version, _merge.CreatorMode); _logger.LogInformation("Input Paths: {Input}", string.Join(", ", _merge.InputPaths.Select(p => p.ToFullPath()))); foreach (string path in _merge.OutputPaths) { - if (!Directory.Exists(path)) + if (!string.IsNullOrEmpty(path) && !Directory.Exists(path)) { _logger.LogDebug("Output path doesn't exist. Creating output path: {Path}", path); Directory.CreateDirectory(path); @@ -39,20 +40,26 @@ public async Task Run() _files.Add(new FileHandler(file)); } - await Task.WhenAll(_files.Select(f => f.ProcessFile(_config.PlatformSettings))); + CSharpParseOptions options = new(_config.PlatformSettings.Lang); - _files = _files.Where(f => !f.Type.HasFlag(FileSettings.Exclude)).OrderBy(f => f.Order).ToList(); + await Task.WhenAll(_files.Select(f => f.ProcessFile(_config.PlatformSettings, options))).ConfigureAwait(false); - BaseCreator creator = BaseCreator.CreateForMode(_config, _files); + _files = _files.Where(f => !f.IsExcludedFile()).OrderBy(f => f.Order).ToList(); + + FileCreator creator = new(_config, _files); if (!creator.Create()) { return; } + + string code = creator.ToCode(); + + // SyntaxNode parsed = await CSharpSyntaxTree.ParseText(code, options).GetRootAsync().ConfigureAwait(false); + // SourceText text = await parsed.NormalizeWhitespace("\n").SyntaxTree.GetTextAsync().ConfigureAwait(false); + // + // code = text.ToString(); - foreach (string file in finalFiles) - { - await File.WriteAllTextAsync(file, creator.ToCode()); - } + await Task.WhenAll(finalFiles.Select(f => File.WriteAllTextAsync(f, code))).ConfigureAwait(false); sw.Stop(); _logger.LogInformation("Merged completed successfully in {Time}ms. {Mode} {Name} saved to: {File}", sw.ElapsedMilliseconds, _merge.CreatorMode, _merge.PluginName, string.Join(", ", finalFiles)); diff --git a/src/PluginMerge/Merge/PluginData.cs b/src/PluginMerge/Merge/PluginData.cs index 186eaa6..576f632 100644 --- a/src/PluginMerge/Merge/PluginData.cs +++ b/src/PluginMerge/Merge/PluginData.cs @@ -1,3 +1,6 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + namespace PluginMerge.Merge; /// <summary> @@ -35,6 +38,11 @@ public class PluginData /// </summary> public string ClassName { get; private set; } + /// <summary> + /// Interfaces On the Plugin + /// </summary> + public List<string> PluginBaseTypes { get; } = new(); + public void SetInfo(string title, string author, string version) { Title = title; @@ -52,4 +60,13 @@ public void SetPluginType(string @namespace, string className) NameSpace = @namespace; ClassName = className; } + + public void SetBaseTypes(SeparatedSyntaxList<BaseTypeSyntax> types) + { + foreach (BaseTypeSyntax typeSyntax in types) + { + string type = typeSyntax.ToString(); + PluginBaseTypes.Add(type); + } + } } \ No newline at end of file diff --git a/src/PluginMerge/PluginMerge.csproj b/src/PluginMerge/PluginMerge.csproj index 4b87e41..5efa52f 100644 --- a/src/PluginMerge/PluginMerge.csproj +++ b/src/PluginMerge/PluginMerge.csproj @@ -11,7 +11,6 @@ <Authors>MJSU</Authors> <ImplicitUsings>enable</ImplicitUsings> <FileVersion>$(Version)</FileVersion> - <TargetFramework>net6.0</TargetFramework> <Title>Plugin Merge true Plugin Merge is a .net 6 CLI tool that allows merging multiple .cs files into a single Oxide / uMod plugin file. @@ -23,6 +22,13 @@ git Oxide uMod Plugin Merge Tool README.md + Default + 7 + latest + true + true + net6.0 + disable @@ -34,12 +40,13 @@ - - - + + + + - + diff --git a/src/PluginMerge/Program.cs b/src/PluginMerge/Program.cs index b0b5f16..e1a0b84 100644 --- a/src/PluginMerge/Program.cs +++ b/src/PluginMerge/Program.cs @@ -6,12 +6,9 @@ internal class Program { private static async Task Main(string[] args) { - ParserResult result = await Parser.Default.ParseArguments(args) - .WithParsedAsync(c => c.Execute()); - - //Allow all log messages to be written before exiting - await Task.Delay(100); - + ParserResult result = await Parser.Default.ParseArguments(args) + .WithParsedAsync(c => c.Execute()).ConfigureAwait(false); + if (result is Parsed parsed) { return ((ICommand)parsed.Value).CloseCode; diff --git a/src/PluginMerge/Rename/RenameHandler.cs b/src/PluginMerge/Rename/RenameHandler.cs new file mode 100644 index 0000000..40e9d20 --- /dev/null +++ b/src/PluginMerge/Rename/RenameHandler.cs @@ -0,0 +1,57 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PluginMerge.Rename; + +public class RenameHandler +{ + public readonly string FileName; + public readonly string PluginName; + private readonly ILogger _logger; + + public RenameHandler(string fileName, string pluginName) + { + FileName = fileName.ToFullPath(); + PluginName = pluginName; + _logger = LogBuilder.GetLogger(); + } + + public async Task Run() + { + if (!File.Exists(FileName)) + { + _logger.LogInformation("Failed to find file '{FileName}'", FileName); + return Constants.CloseCodes.RenameFileNotFound; + } + + string text = await File.ReadAllTextAsync(FileName).ConfigureAwait(false); + + SyntaxTree tree = CSharpSyntaxTree.ParseText(text); + if (await tree.GetRootAsync().ConfigureAwait(false) is not CompilationUnitSyntax root) + { + return Constants.CloseCodes.RenameFileContainsInvalidSource; + } + + bool changed = false; + foreach (NamespaceDeclarationSyntax nameSpace in root.ChildNodes().OfType()) + { + foreach (ClassDeclarationSyntax syntax in nameSpace.ChildNodes().OfType()) + { + if (syntax.BaseList?.Types.Any(type => type.Type.ToString().EndsWith("Plugin")) ?? false) + { + text = string.Concat(text.AsSpan(0, syntax.Identifier.SpanStart), PluginName, text.AsSpan(syntax.Identifier.SpanStart + syntax.Identifier.Span.Length)); + changed = true; + } + } + } + + if (changed) + { + _logger.LogInformation("Successfully renamed class name to {PluginName}", PluginName); + await File.WriteAllTextAsync(FileName, text).ConfigureAwait(false); + } + + return 0; + } +} \ No newline at end of file diff --git a/src/PluginMerge/Scanner/FileScanner.cs b/src/PluginMerge/Scanner/FileScanner.cs index b6ef38f..4178ad4 100644 --- a/src/PluginMerge/Scanner/FileScanner.cs +++ b/src/PluginMerge/Scanner/FileScanner.cs @@ -2,40 +2,43 @@ namespace PluginMerge.Scanner; public class FileScanner { - private readonly List _paths; - private readonly List _ignorePaths; - private readonly List _ignoreFiles; + private readonly List _inputPaths; + private readonly string[] _ignorePaths; + private readonly string[] _ignoreFiles; private readonly string _filter; - private readonly List _files = new(); private readonly ILogger _logger; + + private readonly string _objPath = Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar; + private readonly string _binPath = Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar; - public FileScanner(List paths, string filter = "*", IEnumerable ignorePaths = null, IEnumerable ignoreFiles = null) + public FileScanner(List inputPaths, string filter = "*", IEnumerable ignorePaths = null, IEnumerable ignoreFiles = null) { - ArgumentNullException.ThrowIfNull(paths); + ArgumentNullException.ThrowIfNull(inputPaths); ArgumentNullException.ThrowIfNull(filter); - _paths = paths.Select(p => p.ToFullPath()).ToList(); + _logger = LogBuilder.GetLogger(); _filter = filter; - _ignorePaths = ignorePaths?.Select(p => p.ToFullPath()).ToList(); - _ignoreFiles = ignoreFiles?.Select(p => p.ToFullPath()).ToList(); - _logger = this.GetLogger(); + _inputPaths = inputPaths; + _ignorePaths = ignorePaths?.Select(p => p.ToFullPath()).ToArray() ?? Array.Empty(); + _ignoreFiles = ignoreFiles?.Select(p => p.ToFullPath()).ToArray() ?? Array.Empty(); } - public List ScanFiles() + public IEnumerable ScanFiles() { - foreach (string path in _paths) + foreach (string path in _inputPaths.Select(p => p.ToFullPath())) { - ScanPath(path, path); + foreach (ScannedFile file in ScanPath(path)) + { + yield return file; + } } - - return _files; } - private void ScanPath(string dir, string root) + private IEnumerable ScanPath(string dir) { if (!Directory.Exists(dir)) { _logger.LogError("Directory does not exist: {Dir}", dir); - return; + yield break; } foreach (string file in Directory.GetFiles(dir, _filter, SearchOption.AllDirectories)) @@ -44,20 +47,18 @@ private void ScanPath(string dir, string root) { continue; } - - _files.Add(new ScannedFile(file, root)); + + yield return new ScannedFile(file, dir); } } private bool ShouldIgnorePath(string path) { - return path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}") - || path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") - || (_ignorePaths?.Any(path.StartsWith) ?? false); + return path.Contains(_objPath) || path.Contains(_binPath) || (_inputPaths.Count != 0 && _ignorePaths.Any(path.StartsWith)); } private bool ShouldIgnoreFile(string file) { - return _ignoreFiles != null && _ignoreFiles.Contains(file); + return _ignoreFiles.Length != 0 && _ignoreFiles.Contains(file); } } \ No newline at end of file diff --git a/src/PluginMerge/Scanner/FileInfo.cs b/src/PluginMerge/Scanner/ScannedFile.cs similarity index 100% rename from src/PluginMerge/Scanner/FileInfo.cs rename to src/PluginMerge/Scanner/ScannedFile.cs diff --git a/src/PluginMerge/Creator/CodeWriter.cs b/src/PluginMerge/Writer/CodeWriter.cs similarity index 79% rename from src/PluginMerge/Creator/CodeWriter.cs rename to src/PluginMerge/Writer/CodeWriter.cs index 6ac98f5..544942e 100644 --- a/src/PluginMerge/Creator/CodeWriter.cs +++ b/src/PluginMerge/Writer/CodeWriter.cs @@ -1,6 +1,4 @@ - - -namespace PluginMerge.Creator; +namespace PluginMerge.Writer; /// /// Represents a code write @@ -10,22 +8,21 @@ public class CodeWriter { private int _indent; private readonly StringBuilder _writer; - private readonly FileHandler _plugin; + private readonly PluginData _pluginData; private readonly CodeStyleConfig _style; private readonly string _pluginNameReplacement; /// /// Constructor for the code writer /// - /// - /// - /// - public CodeWriter(FileHandler plugin, CodeStyleConfig style, string pluginNameReplacement) + /// + /// + public CodeWriter(PluginData pluginData, MergeConfig config) { _writer = new StringBuilder(); - _plugin = plugin; - _style = style; - _pluginNameReplacement = $"{pluginNameReplacement}."; + _pluginData = pluginData; + _style = config.CodeStyle; + _pluginNameReplacement = $"{config.PluginName}."; } /// @@ -62,15 +59,35 @@ public void WriteDefines(IEnumerable defines) /// public void WriteUsings(IEnumerable usings) { + bool didWrite = false; foreach (string @using in usings.OrderBy(u => u)) { - _writer.Append("using "); - _writer.Append(@using); - _writer.Append(';'); - _writer.AppendLine(); + didWrite = true; + WriteUsing(@using); } - WriteLine(); + if (didWrite) + { + WriteLine(); + } + } + + public void WriteUsing(string @using) + { + _writer.Append("using "); + _writer.Append(@using); + _writer.Append(';'); + _writer.AppendLine(); + } + + public void WriteUsingAlias(string type, string typeNamespace) + { + _writer.Append("using "); + _writer.Append(type); + _writer.Append(" = "); + _writer.Append(typeNamespace); + _writer.Append(';'); + _writer.AppendLine(); } /// @@ -87,9 +104,8 @@ public void WriteNamespace(string @namespace) /// Writes the start of the class to the code /// /// - /// /// - public void WriteStartClass(string name, string parentClass = null, bool isPartial = false) + public void WriteStartClass(string name, List baseTypes, bool isPartial = false) { WriteIndent(); _writer.Append("public "); @@ -100,10 +116,11 @@ public void WriteStartClass(string name, string parentClass = null, bool isParti _writer.Append("class "); _writer.Append(name); - if (!string.IsNullOrEmpty(parentClass)) + + if (baseTypes != null && baseTypes.Count != 0) { _writer.Append(" : "); - _writer.Append(parentClass); + _writer.Append(string.Join(", ", baseTypes)); } WriteLine(); @@ -121,12 +138,19 @@ public void WriteComment(string comment) _writer.AppendLine(); } + public void WriteDefinition(string definition) + { + WriteIndent(); + _writer.Append(definition); + WriteLine(); + } + /// /// Writes the framework info to the code if is framework /// public void WriteFramework() { - WriteCode(Constants.Definitions.Framework); + WriteDefinition(Constants.Definitions.Framework); } /// @@ -135,9 +159,9 @@ public void WriteFramework() /// public void WriteInfoAttribute(bool comment) { - string title = _plugin?.PluginData?.Title ?? string.Empty; - string author = _plugin?.PluginData?.Author ?? string.Empty; - string version = _plugin?.PluginData?.Version ?? string.Empty; + string title = _pluginData.Title; + string author = _pluginData.Author; + string version = _pluginData.Version; WriteIndent(); if (comment) @@ -155,7 +179,7 @@ public void WriteInfoAttribute(bool comment) /// public void WriteDescriptionAttribute(bool comment) { - string description = _plugin?.PluginData?.Description ?? string.Empty; + string description = _pluginData.Description; WriteIndent(); @@ -280,11 +304,10 @@ public void WriteEndRegion() /// public void WriteIndent() { - if (_indent <= 0) + if (_indent > 0) { - return; + _writer.Append(_style.IndentCharacter, _indent * _style.IndentAmount); } - _writer.Append(new string(_style.IndentCharacter, _indent * _style.IndentAmount)); } /// diff --git a/tools/Local Install.bat b/tools/Local Install.bat index b394b25..3a34dc2 100644 --- a/tools/Local Install.bat +++ b/tools/Local Install.bat @@ -1,5 +1,5 @@ cd ../src/PluginMerge dotnet tool uninstall -g MJSU.Plugin.Merge -dotnet build --configuration Release /p:Version=1.0.0 -dotnet tool install --global --add-source ./bin/nupkg MJSU.Plugin.Merge --version 1.0.0 \ No newline at end of file +dotnet build --configuration Release /p:Version=1.0.5 +dotnet tool install --global --add-source ./bin/nupkg MJSU.Plugin.Merge --version 1.0.5 \ No newline at end of file