mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-03-31 05:12:35 -04:00
Regular Expressions with capture names on coments
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
@@ -58,15 +59,26 @@ public class ConditionalTagCollection<TClass>(bool caseSensitive = true) : TagCo
|
||||
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, Expression conditionExpression)
|
||||
: base(templateTag, conditionExpression)
|
||||
{
|
||||
NameMatcher = new Regex(@$"^<(!)?{templateTag.TagName}->", options | RegexOptions.Compiled);
|
||||
NameCloseMatcher = new Regex($"^<-{templateTag.TagName}>", options | RegexOptions.Compiled);
|
||||
var tagNameRe = TagNameForRegex();
|
||||
NameMatcher = new Regex($"^<(?<not>!)?{tagNameRe}->", options | RegexOptions.Compiled);
|
||||
NameCloseMatcher = new Regex($"^<-{tagNameRe}>", options | RegexOptions.Compiled);
|
||||
CreateConditionExpression = _ => conditionExpression;
|
||||
}
|
||||
|
||||
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, ParameterExpression parameter, Conditional<TClass> conditional)
|
||||
: base(templateTag, Expression.Constant(false))
|
||||
{
|
||||
NameMatcher = new Regex(@$"^<(!)?{templateTag.TagName}(?:\s+?(.*?)\s*?)?->", options);
|
||||
// <property> needs to match on at least one character which is not a space
|
||||
NameMatcher = new Regex($"""
|
||||
(?x) # option x: ignore all unescaped whitespace in pattern and allow comments starting with #
|
||||
^<(?<not>!)? # tags start with a '<'. Condtionals allow an optional ! captured in <not> to negate the condition
|
||||
{TagNameForRegex()} # next the tagname needs to be matched with space being made optional. Also escape all '#'
|
||||
(?:\s+ # the following part is optional. If present it starts with some whitespace
|
||||
(?<property>.+?) # - capture the <property> non greedy so it won't end on whitespace, '[' or '-' (if match is possible)
|
||||
)? # 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);
|
||||
|
||||
CreateConditionExpression = condition
|
||||
|
||||
@@ -141,7 +141,19 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, PF.PropertyFormatter<TPropertyValue> formatter)
|
||||
: base(templateTag, propertyGetter)
|
||||
{
|
||||
NameMatcher = new Regex(@$"^<{templateTag.TagName.Replace(" ", "\\s*?")}\s*?(?:\[([^\[\]]*?)\]\s*?)?>", options | RegexOptions.Compiled);
|
||||
NameMatcher = new Regex($"""
|
||||
(?x) # option x: ignore all unescaped whitespace in pattern and allow comments starting with #
|
||||
^< # 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 ']'.
|
||||
(?<format> # - capture inner part as <format>
|
||||
[^\]]*? # - match any character except ']'
|
||||
) #
|
||||
\] # - closing the format part
|
||||
)?\s*> # Tags end with '>'
|
||||
"""
|
||||
, options | RegexOptions.Compiled);
|
||||
CreateToStringExpression = (expVal, format) =>
|
||||
Expression.Call(
|
||||
formatter.Target is null ? null : Expression.Constant(formatter.Target),
|
||||
@@ -155,7 +167,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, Func<TPropertyValue, string> toString)
|
||||
: base(templateTag, propertyGetter)
|
||||
{
|
||||
NameMatcher = new Regex(@$"^<{templateTag.TagName.Replace(" ", "\\s*?")}>", options | RegexOptions.Compiled);
|
||||
NameMatcher = new Regex(@$"^<{TagNameForRegex()}>", options | RegexOptions.Compiled);
|
||||
CreateToStringExpression = (expVal, _) =>
|
||||
Expression.Call(
|
||||
toString.Target is null ? null : Expression.Constant(toString.Target),
|
||||
|
||||
@@ -60,6 +60,11 @@ internal abstract class TagBase : IPropertyTag
|
||||
return false;
|
||||
}
|
||||
|
||||
protected string TagNameForRegex()
|
||||
{
|
||||
return TemplateTag.TagName.Replace(" ", @"\s*").Replace("#", @"\#");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[Name = {TemplateTag.TagName}, Type = {ReturnType.Name}]";
|
||||
|
||||
@@ -14,8 +14,7 @@ internal partial interface IListFormat<TList> where TList : IListFormat<TList>
|
||||
|
||||
static IEnumerable<T> Max(string formatString, IEnumerable<T> items)
|
||||
{
|
||||
var maxMatch = MaxRegex().Match(formatString);
|
||||
return maxMatch.Success && int.TryParse(maxMatch.Groups[1].ValueSpan, out var max)
|
||||
return MaxRegex().Match(formatString).TryParseInt("max", out var max)
|
||||
? items.Take(max)
|
||||
: items;
|
||||
}
|
||||
@@ -23,8 +22,8 @@ internal partial interface IListFormat<TList> where TList : IListFormat<TList>
|
||||
|
||||
static IEnumerable<string> FormattedList<T>(string formatString, IEnumerable<T> items, CultureInfo? culture) where T : IFormattable
|
||||
{
|
||||
var format = FormatElement(formatString, TList.FormatRegex);
|
||||
var separator = FormatElement(formatString, SeparatorRegex);
|
||||
var format = TList.FormatRegex().Match(formatString).ResolveValue("format");
|
||||
var separator = SeparatorRegex().Match(formatString).ResolveValue("separator");
|
||||
var formattedItems = FilteredList(formatString, items).Select(ItemFormatter);
|
||||
|
||||
// ReSharper disable PossibleMultipleEnumeration
|
||||
@@ -36,12 +35,6 @@ internal partial interface IListFormat<TList> where TList : IListFormat<TList>
|
||||
// ReSharper restore PossibleMultipleEnumeration
|
||||
|
||||
string ItemFormatter(T n) => n.ToString(format, culture);
|
||||
|
||||
static string? FormatElement(string formatString, Func<Regex> regex)
|
||||
{
|
||||
var match = regex().Match(formatString);
|
||||
return match.Success ? match.Groups[1].Value : null;
|
||||
}
|
||||
}
|
||||
|
||||
static string Join<T>(string formatString, IEnumerable<T> items, CultureInfo? culture) where T : IFormattable
|
||||
@@ -61,10 +54,38 @@ internal partial interface IListFormat<TList> where TList : IListFormat<TList>
|
||||
static abstract Regex FormatRegex();
|
||||
|
||||
/// <summary> Max must have a 1 or 2-digit number </summary>
|
||||
[GeneratedRegex(@"[Mm]ax\(\s*([1-9]\d?)\s*\)")]
|
||||
[GeneratedRegex(@"[Mm]ax\(\s*(?<max>[1-9]\d?)\s*\)")]
|
||||
private static partial Regex MaxRegex();
|
||||
|
||||
/// <summary> Separator can be anything </summary>
|
||||
[GeneratedRegex(@"[Ss]eparator\((.*?)\)")]
|
||||
[GeneratedRegex(@"[Ss]eparator\((?<separator>.*?)\)")]
|
||||
private static partial Regex SeparatorRegex();
|
||||
}
|
||||
|
||||
static class RegExpExtensions
|
||||
{
|
||||
extension(Group group)
|
||||
{
|
||||
public string? ValueOrNull() => group.Success ? group.Value : null;
|
||||
public ReadOnlySpan<char> ValueSpanOrNull() => group.Success ? group.ValueSpan : null;
|
||||
}
|
||||
|
||||
extension(Match match)
|
||||
{
|
||||
public Group Resolve(string? groupName = null)
|
||||
{
|
||||
if (groupName is not null && match.Groups.TryGetValue(groupName, out var group))
|
||||
return group;
|
||||
return match.Groups.Count > 1 ? match.Groups[1] : match.Groups[0];
|
||||
}
|
||||
|
||||
public string? ResolveValue(string? groupName = null) => match.Resolve(groupName).ValueOrNull();
|
||||
|
||||
public bool TryParseInt(string? groupName, out int value)
|
||||
{
|
||||
var span = match.Resolve(groupName).ValueSpanOrNull();
|
||||
|
||||
return int.TryParse(span, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ internal partial class NameListFormat : IListFormat<NameListFormat>
|
||||
|
||||
private static IEnumerable<T> Sort<T>(IEnumerable<T> entries, string formatString, Dictionary<string, Func<T, object?>> formatReplacements)
|
||||
{
|
||||
var sortMatch = SortRegex().Match(formatString);
|
||||
if (!sortMatch.Success) return entries;
|
||||
var pattern = SortRegex().Match(formatString).ResolveValue("pattern");
|
||||
if (pattern is null) return entries;
|
||||
|
||||
IOrderedEnumerable<T>? ordered = null;
|
||||
foreach (Match m in SortTokenizer().Matches(sortMatch.Groups["pattern"].Value))
|
||||
foreach (Match m in SortTokenizer().Matches(pattern!))
|
||||
{
|
||||
// Dictionary is case-insensitive, no ToUpper needed
|
||||
if (!formatReplacements.TryGetValue(m.Groups["token"].Value, out var selector))
|
||||
@@ -50,6 +50,6 @@ internal partial class NameListFormat : IListFormat<NameListFormat>
|
||||
private static partial Regex SortTokenizer();
|
||||
|
||||
/// <summary> Format must have at least one of the string {T}, {F}, {M}, {L}, {S}, or {ID} </summary>
|
||||
[GeneratedRegex($@"[Ff]ormat\((.*?\{{{Token}(?::.*?)?\}}.*?)\)")]
|
||||
[GeneratedRegex($@"[Ff]ormat\((?<format>.*?\{{{Token}(?::.*?)?\}}.*?)\)")]
|
||||
public static partial Regex FormatRegex();
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ internal partial class SeriesListFormat : IListFormat<SeriesListFormat>
|
||||
|
||||
private static IEnumerable<T> Sort<T>(IEnumerable<T> entries, string formatString, Dictionary<string, Func<T, object?>> formatReplacements)
|
||||
{
|
||||
var sortMatch = SortRegex().Match(formatString);
|
||||
if (!sortMatch.Success) return entries;
|
||||
var pattern = SortRegex().Match(formatString).ResolveValue("pattern");
|
||||
if (pattern is null) return entries;
|
||||
|
||||
IOrderedEnumerable<T>? ordered = null;
|
||||
foreach (Match m in SortTokenizer().Matches(sortMatch.Groups["pattern"].Value))
|
||||
foreach (Match m in SortTokenizer().Matches(pattern!))
|
||||
{
|
||||
// Dictionary is case-insensitive, no ToUpper needed
|
||||
if (!formatReplacements.TryGetValue(m.Groups["token"].Value, out var selector))
|
||||
@@ -50,6 +50,6 @@ internal partial class SeriesListFormat : IListFormat<SeriesListFormat>
|
||||
private static partial Regex SortTokenizer();
|
||||
|
||||
/// <summary> Format must have at least one of the string {N}, {#}, {ID} </summary>
|
||||
[GeneratedRegex($@"[Ff]ormat\((.*?\{{{Token}(?::.*?)?\}}.*?)\)")]
|
||||
[GeneratedRegex($@"[Ff]ormat\((?<format>.*?\{{{Token}(?::.*?)?\}}.*?)\)")]
|
||||
public static partial Regex FormatRegex();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user