efcore/tools/Resources.tt

358 строки
11 KiB
Plaintext

<#@ template hostspecific="true" #>
<#@ assembly name="Microsoft.VisualStudio.Interop" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Windows.Forms" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.ComponentModel.Design" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Resources" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="EnvDTE" #>
<#
var model = LoadResources();
var namespaceHint = (string)System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint");
#>
// <auto-generated />
using System;
using System.Reflection;
using System.Resources;
<#
if (!model.NoDiagnostics)
{
#>
using System.Threading;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
<#
}
#>
#nullable enable
namespace <#= namespaceHint #>
{
<#
if (namespaceHint.EndsWith("Internal", StringComparison.Ordinal)
|| model.AccessModifier == "internal")
{
#>
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
<#
}
else
{
#>
/// <summary>
/// <para>
/// String resources used in EF exceptions, etc.
/// </para>
/// <para>
/// These strings are exposed publicly for use by database providers and extensions.
/// It is unusual for application code to need these strings.
/// </para>
/// </summary>
<#
}
#>
<#= model.AccessModifier #> static class <#= model.Class #>
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("<#= model.ResourceName #>", typeof(<#= model.Class #>).Assembly);
<#
foreach (var resource in model.Resources)
{
if (!resource.ForLogging)
{
#>
/// <summary>
<#
foreach (var line in Lines(resource.Value))
{
#>
/// <#= Xml(line) #>
<#
}
#>
/// </summary>
<#
if (resource.Obsolete)
{
#>
[Obsolete]
<#
}
if (resource.Parameters.Any())
{
#>
public static string <#= resource.Name #>(<#= List("object? ", resource.Parameters.Select(e => e.ParamString)) #>)
=> string.Format(
GetString("<#= resource.Name #>", <#= List(resource.Parameters.Select(e => e.NameOfString)) #>),
<#= List(resource.Parameters.Select(e => e.ParamString)) #>);
<#
}
else
{
#>
public static string <#= resource.Name #>
=> GetString("<#= resource.Name #>");
<#
}
}
}
#>
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name)!;
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
return value;
}
}
}
<#
if (!model.NoDiagnostics)
{
#>
namespace <#= namespaceHint.EndsWith(".Internal", StringComparison.Ordinal) ? namespaceHint : namespaceHint + ".Internal" #>
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
<#= model.AccessModifier #> static class <#= model.DiagnosticsClass #>
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("<#= model.ResourceName #>", typeof(<#= model.DiagnosticsClass #>).Assembly);
<#
foreach (var resource in model.Resources)
{
if (resource.ForLogging)
{
#>
/// <summary>
<#
foreach (var line in Lines(resource.Value))
{
#>
/// <#= Xml(line) #>
<#
}
#>
/// </summary>
<#
if (resource.Types.Count() > 6)
{
#>
public static FallbackEventDefinition <#= resource.Name #>(IDiagnosticsLogger logger)
{
var definition = ((<#= model.LoggingDefinitionsClass #>)logger.Definitions).<#= resource.Name #>;
if (definition == null)
{
definition = NonCapturingLazyInitializer.EnsureInitialized(
ref ((<#= model.LoggingDefinitionsClass #>)logger.Definitions).<#= resource.Name #>,
logger,
static logger => new FallbackEventDefinition(
logger.Options,
<#= resource.EventId #>,
LogLevel.<#= resource.Level #>,
"<#= resource.EventId #>",
_resourceManager.GetString("<#= resource.Name #>")!));
}
return (FallbackEventDefinition)definition;
}
<#
}
else
{
var genericTypes = resource.Types.Any() ? ("<" + List(resource.Types) + ">") : "";
if (resource.Obsolete)
{
#>
[Obsolete]
<#
}
#>
public static EventDefinition<#= genericTypes #> <#= resource.Name #>(IDiagnosticsLogger logger)
{
var definition = ((<#= model.LoggingDefinitionsClass #>)logger.Definitions).<#= resource.Name #>;
if (definition == null)
{
definition = NonCapturingLazyInitializer.EnsureInitialized(
ref ((<#= model.LoggingDefinitionsClass #>)logger.Definitions).<#= resource.Name #>,
logger,
static logger => new EventDefinition<#= genericTypes #>(
logger.Options,
<#= resource.EventId #>,
LogLevel.<#= resource.Level #>,
"<#= resource.EventId #>",
level => LoggerMessage.Define<#= genericTypes #>(
level,
<#= resource.EventId #>,
_resourceManager.GetString("<#= resource.Name #>")!)));
}
return (EventDefinition<#= genericTypes #>)definition;
}
<#
}
}
}
#>
}
}
<#
}
#>
<#+
ResourceFile LoadResources()
{
var result = new ResourceFile();
if (Session.ContainsKey("AccessModifier"))
{
result.AccessModifier = (string)Session["AccessModifier"];
};
if (Session.ContainsKey("LoggingDefinitionsClass"))
{
result.LoggingDefinitionsClass = (string)Session["LoggingDefinitionsClass"];
};
if (!Session.TryGetValue("NoDiagnostics", out var noDiagnostics))
{
noDiagnostics = false;
}
result.NoDiagnostics = (bool)noDiagnostics;
var resourceFile = (string)Session["ResourceFile"];
if (!Path.IsPathRooted(resourceFile))
{
resourceFile = Host.ResolvePath(resourceFile);
}
var resourceNamespace = (string)Session["ResourceNamespace"];
result.Class = Path.GetFileNameWithoutExtension(resourceFile);
result.DiagnosticsClass = result.Class.Replace("Strings", "Resources");
result.ResourceName = resourceNamespace + "." + result.Class;
List<ResXDataNode> sortedResources;
using (var reader = new ResXResourceReader(resourceFile))
{
reader.UseResXDataNodes = true;
sortedResources = (from DictionaryEntry r in reader
orderby r.Key
select (ResXDataNode)r.Value).ToList();
result.Resources = sortedResources
.Select(r => new Resource(r))
.ToList();
}
using (var writer = new ResXResourceWriter(resourceFile))
{
foreach (var node in sortedResources)
writer.AddResource(node);
writer.Generate();
}
return result;
}
static IEnumerable<string> Lines(string value)
=> value.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
static string Xml(string value)
=> value.Replace("<", "&lt;").Replace(">", "&gt;");
static string List(IEnumerable<string> items)
=> List(null, items);
static string List(string prefix, IEnumerable<string> items, string suffix = null)
=> string.Join(", ", items.Select(i => prefix + i + suffix));
class ResourceFile
{
public string AccessModifier { get; set; } = "public";
public string Class { get; set; }
public string DiagnosticsClass { get; set; }
public string ResourceName { get; set; }
public IEnumerable<Resource> Resources { get; set; }
public bool NoDiagnostics { get; set; }
public string LoggingDefinitionsClass { get; set; }
}
class Resource
{
public Resource(ResXDataNode node)
{
Name = node.Name;
Value = (string)node.GetValue((ITypeResolutionService)null);
var parameters = Regex.Matches(Value, @"\{(\w+)\}")
.Cast<Match>()
.Select(m => m.Groups[1].Value)
.Distinct()
.Select(n => ("nameof(" + n + ")", n))
.ToList();
foreach (var parameter in parameters.ToList())
{
var rawString = parameter.Item2;
var underscoreIndex = rawString.IndexOf('_');
if (underscoreIndex > 0)
{
var newIndex = int.Parse(rawString.Substring(0, underscoreIndex));
parameters[newIndex] = ("\"" + rawString + "\"", rawString.Substring(underscoreIndex + 1));
}
}
Parameters = parameters;
var eventInfo = node.Comment.Split(' ');
var argumentsRead = 0;
if (eventInfo.FirstOrDefault() == "Obsolete")
{
Obsolete = true;
argumentsRead++;
}
Level = eventInfo.Skip(argumentsRead++).FirstOrDefault() ?? "BadLevel";
EventId = eventInfo.Skip(argumentsRead++).FirstOrDefault() ?? "BadEventId";
Types = eventInfo.Skip(argumentsRead++).ToList();
}
public string Name { get; }
public string Value { get; }
public string EventId { get; }
public string Level { get; }
public bool Obsolete { get; }
public bool ForLogging => Name.StartsWith("Log", StringComparison.Ordinal);
public IEnumerable<(string NameOfString, string ParamString)> Parameters { get; }
public IEnumerable<string> Types { get; }
}
#>