mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-03-30 21:01:38 -04:00
Add numerical checks for conditional tag.
- numbers are compared with their value - strings are compared by checking the length - Collections are evaluated on cheking their size
This commit is contained in:
@@ -160,20 +160,38 @@ public partial class ConditionalTagCollection<TClass>(bool caseSensitive = true)
|
||||
var match = CheckRegex().Match(checkString);
|
||||
|
||||
var valStr = match.Groups["val"].Value;
|
||||
var ival = -1;
|
||||
var isNumop = match.Groups["numop"].Success && int.TryParse(valStr, out ival);
|
||||
|
||||
var checkItem = match.Groups["op"].ValueSpan switch
|
||||
{
|
||||
"=" or "" => (v, culture) => VComparedToStr(v, culture, valStr) == 0,
|
||||
"!=" or "!" => (v, culture) => VComparedToStr(v, culture, valStr) != 0,
|
||||
"~" => GetRegExpCheck(valStr),
|
||||
"#=" => (v, _) => VAsInt(v) == ival,
|
||||
"#!=" => (v, _) => VAsInt(v) != ival,
|
||||
"#>=" or ">=" => (v, _) => VAsInt(v) >= ival,
|
||||
"#>" or ">" => (v, _) => VAsInt(v) > ival,
|
||||
"#<=" or "<=" => (v, _) => VAsInt(v) <= ival,
|
||||
"#<" or "<" => (v, _) => VAsInt(v) < ival,
|
||||
_ => DefaultPredicate,
|
||||
};
|
||||
return (v, culture) => v switch
|
||||
{
|
||||
null => false,
|
||||
IEnumerable<object> e => e.Any(o => checkItem(o, culture)),
|
||||
_ => checkItem(v, culture)
|
||||
};
|
||||
return isNumop
|
||||
? (v, culture) => v switch
|
||||
{
|
||||
null => false,
|
||||
IEnumerable<object> e => checkItem(e.Count(), culture),
|
||||
string s => checkItem(s.Length, culture),
|
||||
_ => checkItem(v, culture)
|
||||
}
|
||||
: (v, culture) => v switch
|
||||
{
|
||||
null => false,
|
||||
IEnumerable<object> e => e.Any(o => checkItem(o, culture)),
|
||||
_ => checkItem(v, culture)
|
||||
};
|
||||
|
||||
int? VAsInt(object v) => v is int iv ? iv : int.TryParse(v.ToString(), out var parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
private static int VComparedToStr(object? v, CultureInfo? culture, string valStr)
|
||||
@@ -239,10 +257,12 @@ public partial class ConditionalTagCollection<TClass>(bool caseSensitive = true)
|
||||
(?x) # option x: ignore all unescaped whitespace in pattern and allow comments starting with #
|
||||
^\s* # anchor at start of line trimming leading whitespace
|
||||
(?<op> # capture operator in <op> and <numop>
|
||||
~|!=?|=? # - string comparison operators including ~ for regexp. No operator is like =
|
||||
(?<numop>\#=|\#!=|\#?>=|\#?>|\#?<=|\#?<) # - numerical operators start with a # and might be omitted if unique
|
||||
| ~|!=?|=? # - string comparison operators including ~ for regexp. No operator is like =
|
||||
) \s* # ignore space between operator and value
|
||||
(?<val> # capture value in <val>
|
||||
.*? # - string for comparison. May be empty. Non-greedy capture resulting in no whitespace at the end
|
||||
(?<val>(?(numop) # capture value in <val>
|
||||
\d+ # - numerical operators have to be followed by a number
|
||||
| .*? ) # - string for comparison. May be empty. Non-greedy capture resulting in no whitespace at the end
|
||||
)\s*$ # trimming up to the end
|
||||
""")]
|
||||
private static partial Regex CheckRegex();
|
||||
|
||||
@@ -22,7 +22,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
|
||||
if (formatter.Method.ReturnType != typeof(string)
|
||||
|| parameters.Length != 4
|
||||
|| parameters[0].ParameterType != typeof(ITemplateTag)
|
||||
|| parameters[0].ParameterType != typeof(ITemplateTag)
|
||||
|| parameters[2].ParameterType != typeof(string)
|
||||
|| !typeof(CultureInfo).IsAssignableFrom(parameters[3].ParameterType))
|
||||
throw new ArgumentException(
|
||||
@@ -183,7 +183,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
{
|
||||
public override Regex NameMatcher { get; }
|
||||
private Func<Expression, string?, Expression> CreateToStringExpression { get; }
|
||||
private Func<Expression, string?, Expression> CreateToObjectExpression { get; }
|
||||
private Func<Expression, string?, Expression> CreateToObjectExpression { get; } = (expVal, _) => expVal;
|
||||
|
||||
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, PF.PropertyFormatter<TPropertyValue, TPreFormatted> preFormatter,
|
||||
PF.PropertyFinalizer<TPreFormatted> finalizer, PF.PropertyFinalizer<TPropertyValue> formatter)
|
||||
@@ -204,6 +204,8 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
"""
|
||||
, options);
|
||||
|
||||
// if no format is specified, we can directly use the expVal from the property-getter as object value,
|
||||
// otherwise we need to call the preFormatter with the format string and culture info to get the formatted value as object.
|
||||
CreateToObjectExpression = (expVal, format) =>
|
||||
format is null
|
||||
? expVal
|
||||
@@ -215,6 +217,9 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
Expression.Constant(format),
|
||||
CultureParameter);
|
||||
|
||||
// if no format is specified, we can use the specific formatter to format the value to string directly,
|
||||
// otherwise we need to call the preFormatter with the format string and culture info to get the formatted value as object,
|
||||
// and then call the finalizer to get the final string value.
|
||||
CreateToStringExpression = (expVal, format) =>
|
||||
format is null
|
||||
? Expression.Call(
|
||||
@@ -241,7 +246,6 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
: base(templateTag, propertyGetter)
|
||||
{
|
||||
NameMatcher = new Regex(@$"^<{TagNameForRegex()}>", options);
|
||||
CreateToObjectExpression = (expVal, _) => expVal;
|
||||
|
||||
CreateToStringExpression = (expVal, _) =>
|
||||
Expression.Call(
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace TemplatesTests
|
||||
[DataRow("4<bitrate> - <bitrate> 4", "", "", "1 8 - 1 8")]
|
||||
[DataRow("<channels><channels><samplerate><channels><channels>", "", "", "100")]
|
||||
[DataRow(" <channels> <channels> <samplerate> <channels> <channels>", "", "", "100")]
|
||||
[DataRow(" <channels> - <channels> <samplerate> <channels> - <channels>", "", "", "- 100 -")]
|
||||
[DataRow(" <channels> - <channels> <samplerate> <channels> - <channels>", "", "", "- 100 -")]
|
||||
|
||||
public void Tests_removeSpaces(string template, string dirFullPath, string extension, string expected)
|
||||
{
|
||||
@@ -400,26 +400,34 @@ namespace TemplatesTests
|
||||
[DataRow("<has libation version->empty-string<-has>", "")]
|
||||
[DataRow("<is libation version[=foobar]->empty-string<-has>", "")]
|
||||
[DataRow("<is libation version[=]->empty-string<-has>", "empty-string")]
|
||||
[DataRow("<is libation version[#=0]->empty-string<-has>", "empty-string")]
|
||||
[DataRow("<is libation version[]->empty-string<-has>", "empty-string")]
|
||||
[DataRow("<has file version->null-string<-has>", "")]
|
||||
[DataRow("<has file version[=foobar]->null-string<-has>", "")]
|
||||
[DataRow("<has file version[=]->null-string<-has>", "")]
|
||||
[DataRow("<is file version[#=0]->null-string<-has>", "")]
|
||||
[DataRow("<has file version[]->null-string<-has>", "")]
|
||||
[DataRow("<has year->null-int<-has>", "")]
|
||||
[DataRow("<is year[=]->null-int<-has>", "")]
|
||||
[DataRow("<is year[#=0]->null-int<-has>", "")]
|
||||
[DataRow("<is year[0]->null-int<-has>", "")]
|
||||
[DataRow("<is year[]->null-int<-has>", "")]
|
||||
[DataRow("<has FAKE->unknown-tag<-has>", "")]
|
||||
[DataRow("<is FAKE[=]->unknown-tag<-has>", "")]
|
||||
[DataRow("<is FAKE[=foobar]->unknown-tag<-has>", "")]
|
||||
[DataRow("<is FAKE[#=0]->unknown-tag<-has>", "")]
|
||||
[DataRow("<is FAKE[]->unknown-tag<-has>", "")]
|
||||
[DataRow("<has narrator->empty-list<-has>", "")]
|
||||
[DataRow("<is narrator[=foobar]->empty-list<-has>", "")]
|
||||
[DataRow("<is narrator[=]->empty-list<-has>", "")]
|
||||
[DataRow("<is narrator[~.*]->empty-list<-has>", "")]
|
||||
[DataRow("<is narrator[<1]->empty-list<-has>", "empty-list")]
|
||||
[DataRow("<is narrator[#=0]->empty-list<-has>", "empty-list")]
|
||||
[DataRow("<is narrator[]->empty-list<-has>", "")]
|
||||
[DataRow("<is first narrator->no-first<-has>", "")]
|
||||
[DataRow("<is first narrator[=foobar]->no-first<-has>", "")]
|
||||
[DataRow("<is first narrator[=]->no-first<-has>", "")]
|
||||
[DataRow("<is first narrator[#=0]->no-first<-has>", "")]
|
||||
[DataRow("<is first narrator[]->no-first<-has>", "")]
|
||||
public void HasValue_on_empty_test(string template, string expected)
|
||||
{
|
||||
@@ -468,13 +476,21 @@ namespace TemplatesTests
|
||||
[DataRow("<has ch# 0->true<-has>", "true")]
|
||||
[DataRow("<is title[=A Study in Scarlet: An Audible Original Drama]->true<-has>", "true")]
|
||||
[DataRow("<is title[U][=A STUDY IN SCARLET: AN AUDIBLE ORIGINAL DRAMA]->true<-has>", "true")]
|
||||
[DataRow("<is title[#=45]->true<-has>", "true")]
|
||||
[DataRow("<is title[!=foo]->true<-has>", "true")]
|
||||
[DataRow("<is title[~A Study.*]->true<-has>", "true")]
|
||||
[DataRow("<is title[foo]->true<-has>", "")]
|
||||
[DataRow("<is ch count[>=1]->true<-has>", "true")]
|
||||
[DataRow("<is ch count[>1]->true<-has>", "true")]
|
||||
[DataRow("<is ch count[<=100]->true<-has>", "true")]
|
||||
[DataRow("<is ch count[<100]->true<-has>", "true")]
|
||||
[DataRow("<is ch count[=2]->true<-has>", "true")]
|
||||
[DataRow("<is author[>=2]->true<-has>", "true")]
|
||||
[DataRow("<is author[#=2]->true<-has>", "true")]
|
||||
[DataRow("<is author[=Arthur Conan Doyle]->true<-has>", "true")]
|
||||
[DataRow("<is author[format({L})][=Doyle]->true<-has>", "true")]
|
||||
[DataRow("<is author[format({L})separator(:)][=Doyle:Fry]->true<-has>", "true")]
|
||||
[DataRow("<is author[>=3]->true<-has>", "")]
|
||||
[DataRow("<is author[=Sherlock]->true<-has>", "")]
|
||||
public void HasValue_test(string template, string expected)
|
||||
{
|
||||
|
||||
@@ -185,9 +185,17 @@ You can use custom formatters to construct customized DateTime string. For more
|
||||
| =STRING **†** | Matches if one item is equal to STRING (case ignored) | <has tag[=Tag1]-> |
|
||||
| !=STRING **†** | Matches if one item is not equal to STRING (case ignored) | <has first author[!=Arthur]-> |
|
||||
| ~STRING **†** | Matches if one items is matched by the regular expression STRING (case ignored) | <has title[~(\[XYZ\]).*\\1]-> |
|
||||
| #=NUMBER **‡** | Matches if the number value is equal to NUMBER | <has channels[#=2]-> |
|
||||
| #!=NUMBER **‡** | Matches if the number value is not equal to NUMBER | <has author[#!=1]-> |
|
||||
| #>=NUMBER **‡** | Matches if the number value is greater than or equal to NUMBER | <has bitrate[#>=128]-> |
|
||||
| #>NUMBER **‡** | Matches if the number value is greater than NUMBER | <has title[#>30]-> |
|
||||
| #<=NUMBER **‡** | Matches if the number value is less than or equal to NUMBER | <has first narrator[format({F})][#<=1]-> |
|
||||
| #<NUMBER **‡** | Matches if the number value is less than NUMBER | <has author[#<3]-> |
|
||||
|
||||
**†** STRING maybe escaped with a backslash. So even square brackets could be used. If a single backslash should be part of the string, it must be doubled.
|
||||
|
||||
**‡** NUMBER checks on lists are checking the size of the list. If the value to check is a string, its length is used.
|
||||
|
||||
#### More complex examples
|
||||
|
||||
This example will truncate the title to 4 characters and check its (trimmed) value to be "the" in any case:
|
||||
|
||||
Reference in New Issue
Block a user