Skip to content

Commit

Permalink
Adds startswith/endswith operators (#280)
Browse files Browse the repository at this point in the history
  • Loading branch information
TFleury authored Mar 1, 2021
1 parent a8fdf70 commit 46d76d5
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 16 deletions.
36 changes: 36 additions & 0 deletions Fluid.Tests/BinaryExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,42 @@ public Task GreaterThanBinaryExpressionIsEvaluated(string source, string expecte
return CheckAsync(source, expected);
}

[Theory]
[InlineData("'abc' startswith 'bc'", "false")]
[InlineData("'abc' startswith 'ab'", "true")]
[InlineData("x startswith 'b'", "false")]
[InlineData("x startswith 'a'", "true")]
[InlineData("y startswith 2", "false")]
[InlineData("y startswith 1", "true")]
[InlineData("z startswith 'a'", "false")]
public Task StartsWithBinaryExpressionIsEvaluated(string source, string expected)
{
return CheckAsync(source, expected, context =>
{
context.SetValue("x", new[] { "a", "b", "c" });
context.SetValue("y", new[] { 1, 2, 3 });
context.SetValue("z", new string[0]);
});
}

[Theory]
[InlineData("'abc' endswith 'ab'", "false")]
[InlineData("'abc' endswith 'bc'", "true")]
[InlineData("x endswith 'b'", "false")]
[InlineData("x endswith 'c'", "true")]
[InlineData("y endswith 2", "false")]
[InlineData("y endswith 3", "true")]
[InlineData("z endswith 'a'", "false")]
public Task EndsWithBinaryExpressionIsEvaluated(string source, string expected)
{
return CheckAsync(source, expected, context =>
{
context.SetValue("x", new[] { "a", "b", "c" });
context.SetValue("y", new[] { 1, 2, 3 });
context.SetValue("z", new string[0]);
});
}

[Theory]
[InlineData("'' == empty", "true")]
[InlineData("'a' == empty", "false")]
Expand Down
4 changes: 2 additions & 2 deletions Fluid.Tests/Extensibility/ExtensibilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ public void ShouldAddOperator()
{
var parser = new CustomParser();

parser.RegisteredOperators["startsWith"] = (a, b) => new StartsWithBinaryExpression(a, b);
parser.RegisteredOperators["xor"] = (a, b) => new XorBinaryExpression(a, b);

parser.TryParse("{% if 'abc' startsWith 'ab' %}true{% endif %}", out var template, out var error);
parser.TryParse("{% if true xor false %}true{% endif %}", out var template, out var error);

Assert.Equal("true", template.Render());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace Fluid.Tests.Extensibility
{
public class StartsWithBinaryExpression : BinaryExpression
public class XorBinaryExpression : BinaryExpression
{
public StartsWithBinaryExpression(Expression left, Expression right) : base(left, right)
public XorBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

Expand All @@ -15,9 +15,7 @@ public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext contex
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

return leftValue.ToStringValue().StartsWith(rightValue.ToStringValue())
? BooleanValue.True
: BooleanValue.False;
return BooleanValue.Create(leftValue.ToBooleanValue() ^ rightValue.ToBooleanValue());
}
}
}
32 changes: 32 additions & 0 deletions Fluid/Ast/BinaryExpressions/EndsWithBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Fluid.Values;
using System.Threading.Tasks;

namespace Fluid.Ast.BinaryExpressions
{
public class EndsWithBinaryExpression : BinaryExpression
{
public EndsWithBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

if (leftValue is ArrayValue)
{
var first = await leftValue.GetValueAsync("last", context);
return first.Equals(rightValue)
? BooleanValue.True
: BooleanValue.False;
}
else
{
return leftValue.ToStringValue().EndsWith(rightValue.ToStringValue())
? BooleanValue.True
: BooleanValue.False;
}
}
}
}
32 changes: 32 additions & 0 deletions Fluid/Ast/BinaryExpressions/StartsWithBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Fluid.Values;
using System.Threading.Tasks;

namespace Fluid.Ast.BinaryExpressions
{
public class StartsWithBinaryExpression : BinaryExpression
{
public StartsWithBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

if (leftValue is ArrayValue)
{
var first = await leftValue.GetValueAsync("first", context);
return first.Equals(rightValue)
? BooleanValue.True
: BooleanValue.False;
}
else
{
return leftValue.ToStringValue().StartsWith(rightValue.ToStringValue())
? BooleanValue.True
: BooleanValue.False;
}
}
}
}
4 changes: 4 additions & 0 deletions Fluid/FluidParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public class FluidParser
protected static readonly Parser<string> GreaterOr = Terms.Text(">=");
protected static readonly Parser<string> LowerOr = Terms.Text("<=");
protected static readonly Parser<string> Contains = Terms.Text("contains");
protected static readonly Parser<string> StartsWith = Terms.Text("startswith");
protected static readonly Parser<string> EndsWith = Terms.Text("endswith");
protected static readonly Parser<string> BinaryOr = Terms.Text("or");
protected static readonly Parser<string> BinaryAnd = Terms.Text("and");

Expand Down Expand Up @@ -100,6 +102,8 @@ public FluidParser()
RegisteredOperators["or"] = (a, b) => new OrBinaryExpression(a, b);
RegisteredOperators["and"] = (a, b) => new AndBinaryExpression(a, b);
RegisteredOperators["contains"] = (a, b) => new ContainsBinaryExpression(a, b);
RegisteredOperators["startswith"] = (a, b) => new StartsWithBinaryExpression(a, b);
RegisteredOperators["endswith"] = (a, b) => new EndsWithBinaryExpression(a, b);
RegisteredOperators["=="] = (a, b) => new EqualBinaryExpression(a, b);
RegisteredOperators["!="] = (a, b) => new NotEqualBinaryExpression(a, b);
RegisteredOperators["<>"] = (a, b) => new NotEqualBinaryExpression(a, b);
Expand Down
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,9 @@ Operator are used to compare values, like `>` or `contains`. Custom operators ca

#### Source

The following example creates a custom `startsWith` operator that will evaluate to `true` if the left expression starts with the right expression when converted to strings.
The following example creates a custom `xor` operator that will evaluate to `true` if only one of the left and right expressions is true when converted to booleans.

__StartsWithExpression.cs__
__XorBinaryExpression.cs__

```csharp
using Fluid.Ast;
Expand All @@ -515,9 +515,9 @@ using System.Threading.Tasks;

namespace Fluid.Tests.Extensibility
{
public class StartsWithBinaryExpression : BinaryExpression
public class XorBinaryExpression : BinaryExpression
{
public StartsWithBinaryExpression(Expression left, Expression right) : base(left, right)
public XorBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

Expand All @@ -526,9 +526,7 @@ namespace Fluid.Tests.Extensibility
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

return leftValue.ToStringValue().StartsWith(rightValue.ToStringValue())
? BooleanValue.True
: BooleanValue.False;
return BooleanValue.Create(leftValue.ToBooleanValue() ^ rightValue.ToBooleanValue());
}
}
}
Expand All @@ -537,13 +535,13 @@ namespace Fluid.Tests.Extensibility
__Parser configuration__

```csharp
parser.RegisteredOperators["startsWith"] = (a, b) => new StartsWithBinaryExpression(a, b);
parser.RegisteredOperators["xor"] = (a, b) => new XorBinaryExpression(a, b);
```

__Usage__

```Liquid
{% if 'abc' startsWith 'ab' %}Hello{% endif %}
{% if true xor false %}Hello{% endif %}
```

#### Result
Expand Down

0 comments on commit 46d76d5

Please sign in to comment.