mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-03-30 04:45:05 -04:00
Formats are now escapable and the evaluation of HasValue has been transformed into a configuration parameter.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
@@ -24,7 +23,9 @@ internal interface IClosingPropertyTag : IPropertyTag
|
||||
bool StartsWithClosing(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? propertyTag);
|
||||
}
|
||||
|
||||
public delegate bool Conditional<T>(ITemplateTag templateTag, T value, string condition, CultureInfo? culture);
|
||||
public delegate string? ValueProvider<in T>(ITemplateTag templateTag, T value, string condition, CultureInfo? culture);
|
||||
|
||||
public delegate bool ConditionEvaluator(string? value, CultureInfo? culture);
|
||||
|
||||
public class ConditionalTagCollection<TClass>(bool caseSensitive = true) : TagCollection(typeof(TClass), caseSensitive)
|
||||
{
|
||||
@@ -44,10 +45,11 @@ public class ConditionalTagCollection<TClass>(bool caseSensitive = true) : TagCo
|
||||
/// Register a conditional tag.
|
||||
/// </summary>
|
||||
/// <param name="templateTag"></param>
|
||||
/// <param name="conditional">A <see cref="Conditional{TClass}"/> to get the condition's <see cref="bool"/> value</param>
|
||||
public void Add(ITemplateTag templateTag, Conditional<TClass> conditional)
|
||||
/// <param name="valueProvider">A <see cref="ValueProvider{T}"/> to get the condition's <see cref="bool"/> value</param>
|
||||
/// <param name="conditionEvaluator"></param>
|
||||
public void Add(ITemplateTag templateTag, ValueProvider<TClass> valueProvider, ConditionEvaluator conditionEvaluator)
|
||||
{
|
||||
AddPropertyTag(new ConditionalTag(templateTag, Options, Parameter, conditional));
|
||||
AddPropertyTag(new ConditionalTag(templateTag, Options, Parameter, valueProvider, conditionEvaluator));
|
||||
}
|
||||
|
||||
private class ConditionalTag : TagBase, IClosingPropertyTag
|
||||
@@ -61,12 +63,12 @@ public class ConditionalTagCollection<TClass>(bool caseSensitive = true) : TagCo
|
||||
: base(templateTag, conditionExpression)
|
||||
{
|
||||
var tagNameRe = TagNameForRegex();
|
||||
NameMatcher = new Regex($"^<(?<not>!)?{tagNameRe}->", options | RegexOptions.Compiled);
|
||||
NameCloseMatcher = new Regex($"^<-{tagNameRe}>", options | RegexOptions.Compiled);
|
||||
NameMatcher = new Regex($"^<(?<not>!)?{tagNameRe}->", options);
|
||||
NameCloseMatcher = new Regex($"^<-{tagNameRe}>", options);
|
||||
CreateConditionExpression = _ => conditionExpression;
|
||||
}
|
||||
|
||||
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, ParameterExpression parameter, Conditional<TClass> conditional)
|
||||
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, ParameterExpression parameter, ValueProvider<TClass> valueProvider, ConditionEvaluator conditionEvaluator)
|
||||
: base(templateTag, Expression.Constant(false))
|
||||
{
|
||||
// <property> needs to match on at least one character which is not a space
|
||||
@@ -79,17 +81,32 @@ public class ConditionalTagCollection<TClass>(bool caseSensitive = true) : TagCo
|
||||
)? # end of optional property and check part
|
||||
\s*-> # Opening tags end with '->' and closing tags begin with '<-', so both sides visually point toward each other
|
||||
"""
|
||||
, options | RegexOptions.Compiled);
|
||||
NameCloseMatcher = new Regex($"^<-{templateTag.TagName}>", options | RegexOptions.Compiled);
|
||||
, options);
|
||||
NameCloseMatcher = new Regex($"^<-{templateTag.TagName}>", options);
|
||||
|
||||
CreateConditionExpression = condition
|
||||
=> Expression.Call(
|
||||
conditional.Target is null ? null : Expression.Constant(conditional.Target),
|
||||
conditional.Method,
|
||||
Expression.Constant(templateTag),
|
||||
parameter,
|
||||
Expression.Constant(condition),
|
||||
CultureParameter);
|
||||
CreateConditionExpression = property
|
||||
=> ConditionEvaluatorCall(templateTag, parameter, valueProvider, property, conditionEvaluator);
|
||||
}
|
||||
|
||||
private static MethodCallExpression ConditionEvaluatorCall(ITemplateTag templateTag, ParameterExpression parameter, ValueProvider<TClass> valueProvider, string? property,
|
||||
ConditionEvaluator conditionEvaluator)
|
||||
{
|
||||
return Expression.Call(
|
||||
conditionEvaluator.Target is null ? null : Expression.Constant(conditionEvaluator.Target),
|
||||
conditionEvaluator.Method,
|
||||
ValueProviderCall(templateTag, parameter, valueProvider, property),
|
||||
CultureParameter);
|
||||
}
|
||||
|
||||
private static MethodCallExpression ValueProviderCall(ITemplateTag templateTag, ParameterExpression parameter, ValueProvider<TClass> valueProvider, string? property)
|
||||
{
|
||||
return Expression.Call(
|
||||
valueProvider.Target is null ? null : Expression.Constant(valueProvider.Target),
|
||||
valueProvider.Method,
|
||||
Expression.Constant(templateTag),
|
||||
parameter,
|
||||
Expression.Constant(property),
|
||||
CultureParameter);
|
||||
}
|
||||
|
||||
public bool StartsWithClosing(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? propertyTag)
|
||||
|
||||
@@ -146,14 +146,16 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
^< # tags start with a '<'
|
||||
{TagNameForRegex()} # next the tagname needs to be matched with space being made optional. Also escape all '#'
|
||||
(?:\s* # optional whitespace
|
||||
\[ # optional format details enclosed in '[' and ']'.
|
||||
\[\s* # optional format details enclosed in '[' and ']'. Format shall be trimmed. So match whitespace first
|
||||
(?<format> # - capture inner part as <format>
|
||||
[^\]]*? # - match any character except ']'
|
||||
) #
|
||||
(?:\\. # - '\' escapes allways the next character. Especially further '\' and the closing ']'
|
||||
|[^\\\]])*? # - match any character except '\' and ']' non greedy so the match won't end whith whitespace
|
||||
)\s* # - the whitespace after the format is optional
|
||||
\] # - closing the format part
|
||||
)?\s*> # Tags end with '>'
|
||||
"""
|
||||
, options | RegexOptions.Compiled);
|
||||
, options);
|
||||
|
||||
CreateToStringExpression = (expVal, format) =>
|
||||
Expression.Call(
|
||||
formatter.Target is null ? null : Expression.Constant(formatter.Target),
|
||||
@@ -167,7 +169,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, Func<TPropertyValue, string> toString)
|
||||
: base(templateTag, propertyGetter)
|
||||
{
|
||||
NameMatcher = new Regex(@$"^<{TagNameForRegex()}>", options | RegexOptions.Compiled);
|
||||
NameMatcher = new Regex(@$"^<{TagNameForRegex()}>", options);
|
||||
CreateToStringExpression = (expVal, _) =>
|
||||
Expression.Call(
|
||||
toString.Target is null ? null : Expression.Constant(toString.Target),
|
||||
@@ -177,7 +179,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
|
||||
protected override Expression GetTagExpression(string exactName, Dictionary<string, Group> matchData)
|
||||
{
|
||||
var formatString = matchData.GetValueOrDefault("format")?.Value ?? "";
|
||||
var formatString = Unescape(matchData.GetValueOrDefault("format")) ?? "";
|
||||
|
||||
Expression toStringExpression
|
||||
= !ReturnType.IsValueType
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FileManager.NamingTemplate;
|
||||
@@ -66,6 +67,31 @@ internal abstract class TagBase : IPropertyTag
|
||||
return TemplateTag.TagName.Replace(" ", @"\s*").Replace("#", @"\#");
|
||||
}
|
||||
|
||||
protected static string? Unescape(Group? group)
|
||||
{
|
||||
return group?.Success ?? false ? Unescape(group.ValueSpan) : null;
|
||||
}
|
||||
|
||||
protected static string Unescape(ReadOnlySpan<char> valueSpan)
|
||||
{
|
||||
if (valueSpan.IsEmpty) return "";
|
||||
|
||||
var first = valueSpan.IndexOf('\\');
|
||||
if (first < 0)
|
||||
return valueSpan.ToString();
|
||||
|
||||
var sb = new StringBuilder(valueSpan.Length);
|
||||
sb.Append(valueSpan[..first]);
|
||||
for (var i = first; i < valueSpan.Length; i++)
|
||||
{
|
||||
if (valueSpan[i] == '\\' && i + 1 < valueSpan.Length)
|
||||
i++; // skip backslash and take the next char
|
||||
sb.Append(valueSpan[i]);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[Name = {TemplateTag.TagName}, Type = {ReturnType.Name}]";
|
||||
|
||||
@@ -331,7 +331,7 @@ public abstract class Templates
|
||||
|
||||
private static readonly ConditionalTagCollection<CombinedDto> combinedConditionalTags = new()
|
||||
{
|
||||
{ TemplateTags.Has, HasValue}
|
||||
{ TemplateTags.Has, TryGetValue, HasValue }
|
||||
};
|
||||
|
||||
private static readonly ConditionalTagCollection<LibraryBookDto> folderConditionalTags = new()
|
||||
@@ -342,26 +342,29 @@ public abstract class Templates
|
||||
private static readonly List<TagCollection> allPropertyTags =
|
||||
chapterPropertyTags.Append(filePropertyTags).Append(audioFilePropertyTags).ToList();
|
||||
|
||||
private static bool HasValue(ITemplateTag _, CombinedDto dtos, string property, CultureInfo? culture)
|
||||
private static string? TryGetValue(ITemplateTag _, CombinedDto dtos, string property, CultureInfo? culture)
|
||||
{
|
||||
Func<string?, CultureInfo?, bool> check = (s, _) => !string.IsNullOrWhiteSpace(s);
|
||||
|
||||
foreach (var c in allPropertyTags.OfType<PropertyTagCollection<LibraryBookDto>>())
|
||||
{
|
||||
if (c.TryGetValue(property, dtos.LibraryBook, culture, out var value))
|
||||
return check(value, culture ?? CultureInfo.CurrentCulture);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (dtos.MultiConvert is null)
|
||||
return false;
|
||||
return null;
|
||||
|
||||
foreach (var c in allPropertyTags.OfType<PropertyTagCollection<MultiConvertFileProperties>>())
|
||||
{
|
||||
if (c.TryGetValue(property, dtos.MultiConvert, culture, out var value))
|
||||
return check(value, culture ?? CultureInfo.CurrentCulture);
|
||||
return value;
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool HasValue(string? value, CultureInfo? culture)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -83,29 +83,31 @@ public class GetPortionFilename
|
||||
private readonly ConditionalTagCollection<PropertyClass1> _conditional1 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "ifc1" }, i => i.Condition },
|
||||
{ new TemplateTag { TagName = "has1" }, HasValue }
|
||||
{ new TemplateTag { TagName = "has1" }, TryGetValue, HasValue }
|
||||
};
|
||||
|
||||
private readonly ConditionalTagCollection<PropertyClass2> _conditional2 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "ifc2" }, i => i.Condition },
|
||||
{ new TemplateTag { TagName = "has2" }, HasValue }
|
||||
{ new TemplateTag { TagName = "has2" }, TryGetValue, HasValue }
|
||||
};
|
||||
|
||||
private readonly ConditionalTagCollection<PropertyClass3> _conditional3 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "ifc3" }, i => i.Condition },
|
||||
{ new TemplateTag { TagName = "has3" }, HasValue }
|
||||
{ new TemplateTag { TagName = "has3" }, TryGetValue, HasValue }
|
||||
};
|
||||
|
||||
private static bool HasValue(ITemplateTag templateTag, PropertyClass1 referenceType, string condition, CultureInfo? culture)
|
||||
=> props1.TryGetValue(condition, referenceType, culture, out var value) && !string.IsNullOrEmpty(value);
|
||||
private static string? TryGetValue(ITemplateTag templateTag, PropertyClass1 referenceType, string condition, CultureInfo? culture)
|
||||
=> props1.TryGetValue(condition, referenceType, culture, out var value) ? value : null;
|
||||
|
||||
private static bool HasValue(ITemplateTag templateTag, PropertyClass2 referenceType, string condition, CultureInfo? culture)
|
||||
=> props2.TryGetValue(condition, referenceType, culture, out var value) && !string.IsNullOrEmpty(value);
|
||||
private static string? TryGetValue(ITemplateTag templateTag, PropertyClass2 referenceType, string condition, CultureInfo? culture)
|
||||
=> props2.TryGetValue(condition, referenceType, culture, out var value) ? value : null;
|
||||
|
||||
private static bool HasValue(ITemplateTag templateTag, PropertyClass3 referenceType, string condition, CultureInfo? culture)
|
||||
=> props3.TryGetValue(condition, referenceType, culture, out var value) && !string.IsNullOrEmpty(value);
|
||||
private static string? TryGetValue(ITemplateTag templateTag, PropertyClass3 referenceType, string condition, CultureInfo? culture)
|
||||
=> props3.TryGetValue(condition, referenceType, culture, out var value) ? value : null;
|
||||
|
||||
private static bool HasValue(string? value, CultureInfo? culture) => !string.IsNullOrWhiteSpace(value);
|
||||
|
||||
private readonly PropertyClass1 _propertyClass1 = new()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user