diff --git a/AssetRipper.sln b/AssetRipper.sln index 2aac040d7..0b0ece250 100644 --- a/AssetRipper.sln +++ b/AssetRipper.sln @@ -162,6 +162,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.SmartEnums", "S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.Processing.SourceGenerator", "Source\AssetRipper.Processing.SourceGenerator\AssetRipper.Processing.SourceGenerator.csproj", "{6679D5D0-8D40-44AC-9D20-0FD1303D45A2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetRipper.GUI.Licensing.SourceGenerator", "Source\AssetRipper.GUI.Licensing.SourceGenerator\AssetRipper.GUI.Licensing.SourceGenerator.csproj", "{6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -340,6 +342,10 @@ Global {6679D5D0-8D40-44AC-9D20-0FD1303D45A2}.Debug|Any CPU.Build.0 = Debug|Any CPU {6679D5D0-8D40-44AC-9D20-0FD1303D45A2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6679D5D0-8D40-44AC-9D20-0FD1303D45A2}.Release|Any CPU.Build.0 = Release|Any CPU + {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B3DD564-FD57-4EAA-8696-A15A5A6FFF29}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Source/AssetRipper.GUI.Licensing.SourceGenerator/AssetRipper.GUI.Licensing.SourceGenerator.csproj b/Source/AssetRipper.GUI.Licensing.SourceGenerator/AssetRipper.GUI.Licensing.SourceGenerator.csproj new file mode 100644 index 000000000..ad10dee81 --- /dev/null +++ b/Source/AssetRipper.GUI.Licensing.SourceGenerator/AssetRipper.GUI.Licensing.SourceGenerator.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.0 + false + true + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/Source/AssetRipper.GUI.Licensing.SourceGenerator/LicensesGenerator.cs b/Source/AssetRipper.GUI.Licensing.SourceGenerator/LicensesGenerator.cs new file mode 100644 index 000000000..20f32564c --- /dev/null +++ b/Source/AssetRipper.GUI.Licensing.SourceGenerator/LicensesGenerator.cs @@ -0,0 +1,86 @@ +using AssetRipper.Text.SourceGeneration; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using SGF; +using System.CodeDom.Compiler; +using System.Collections.Immutable; + +namespace AssetRipper.GUI.Licensing.SourceGenerator; + +[IncrementalGenerator] +public sealed class LicensesGenerator() : IncrementalGenerator(nameof(LicensesGenerator)) +{ + public override void OnInitialize(SgfInitializationContext context) + { + IncrementalValueProvider> pipeline = context.AdditionalTextsProvider + .Where(static (text) => text.Path.EndsWith(".md")) + .Select(static (text, cancellationToken) => + { + string name = Path.GetFileNameWithoutExtension(text.Path); + string? license = text.GetText(cancellationToken)?.ToString(); + return (name, license); + }) + .Where(static (pair) => pair.license != null) + .Collect()!; + + context.RegisterSourceOutput(pipeline, GenerateCode); + } + + private static void GenerateCode(SgfSourceProductionContext context, ImmutableArray<(string Name, string License)> array) + { + using StringWriter stringWriter = new(); + using IndentedTextWriter writer = IndentedTextWriterFactory.Create(stringWriter); + + writer.WriteGeneratedCodeWarning(); + writer.WriteLineNoTabs(); + + writer.WriteLine("#nullable enable"); + writer.WriteLineNoTabs(); + writer.WriteFileScopedNamespace("AssetRipper.GUI.Licensing"); + writer.WriteLineNoTabs(); + writer.WriteLine("partial class Licenses"); + using (new CurlyBrackets(writer)) + { + List<(string, string)> sourceNames = new(array.Length); + foreach ((string name, string license) in array.OrderBy(pair => pair.Name)) + { + string sourceName = name.Replace('.', '_').Replace('-', '_'); + sourceNames.Add((name, sourceName)); + writer.WriteLine($"private static global::System.ReadOnlySpan {sourceName}_span => {SymbolDisplay.FormatLiteral(license, true)}u8;"); + writer.WriteLine($"private static string? {sourceName}_field;"); + if (name != sourceName) + { + writer.WriteSummaryDocumentation(name); + } + writer.WriteLine($"public static string {sourceName} => {sourceName}_field ??= global::System.Text.Encoding.UTF8.GetString({sourceName}_span);"); + writer.WriteLineNoTabs(); + } + + // Names list + writer.WriteLine("public static global::System.Collections.Generic.IReadOnlyList Names { get; } ="); + writer.WriteLine('['); + using (new Indented(writer)) + { + foreach ((string name, _) in sourceNames) + { + writer.WriteLine($"{SymbolDisplay.FormatLiteral(name, true)},"); + } + } + writer.WriteLine("];"); + writer.WriteLineNoTabs(); + + // Get method + writer.WriteLine("public static string? TryLoad(string name) => name switch"); + using (new CurlyBracketsWithSemicolon(writer)) + { + foreach ((string name, string sourceName) in sourceNames) + { + writer.WriteLine($"{SymbolDisplay.FormatLiteral(name, true)} => {sourceName},"); + } + writer.WriteLine($"_ => null,"); + } + } + + context.AddSource("Licenses.g.cs", stringWriter.ToString()); + } +} diff --git a/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj b/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj index 40e6b131c..a4b5b52ac 100644 --- a/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj +++ b/Source/AssetRipper.GUI.Licensing/AssetRipper.GUI.Licensing.csproj @@ -4,11 +4,16 @@ true ..\0Bins\Other\AssetRipper.GUI.Licensing\$(Configuration)\ ..\0Bins\obj\AssetRipper.GUI.Licensing\$(Configuration)\ + true - + + + + + diff --git a/Source/AssetRipper.GUI.Licensing/Licenses.cs b/Source/AssetRipper.GUI.Licensing/Licenses.cs index c362d8339..3d2f9edcd 100644 --- a/Source/AssetRipper.GUI.Licensing/Licenses.cs +++ b/Source/AssetRipper.GUI.Licensing/Licenses.cs @@ -1,42 +1,24 @@ -using System.Reflection; +namespace AssetRipper.GUI.Licensing; -namespace AssetRipper.GUI.Licensing; - -public static class Licenses +public static partial class Licenses { - private const string FilePrefix = "AssetRipper.GUI.Licensing."; - - private static Assembly Assembly => typeof(Licenses).Assembly; - - public static IReadOnlyList Names { get; } = Assembly - .GetManifestResourceNames() - .Select(t => t.Substring(FilePrefix.Length, t.Length - FilePrefix.Length - 3)) - .ToArray(); - /// - /// Load a license file from the embedded resources. + /// Load a license file. /// - /// The name of the file without any extension. + /// The name of the license (without any extension). /// The loaded text. - public static string Load(string fileName) + public static string Load(string name) { - if (TryLoad(fileName, out string? license)) + if (TryLoad(name, out string? license)) { return license; } - throw new LicenseNotFoundException(fileName); + throw new LicenseNotFoundException(name); } - public static bool TryLoad(string fileName, [NotNullWhen(true)] out string? license) + public static bool TryLoad(string name, [NotNullWhen(true)] out string? license) { - using Stream? stream = Assembly.GetManifestResourceStream(FilePrefix + fileName + ".md"); - if (stream is null) - { - license = null; - return false; - } - - license = new StreamReader(stream).ReadToEnd(); - return true; + license = TryLoad(name); + return license is not null; } }