Skip to content

Commit e59fd39

Browse files
authored
Add find, find_index, has and reject filters (#780)
c.f. Shopify/liquid#1849
1 parent cc1003b commit e59fd39

File tree

2 files changed

+303
-1
lines changed

2 files changed

+303
-1
lines changed

Fluid.Tests/ArrayFiltersTests.cs

+190
Original file line numberDiff line numberDiff line change
@@ -659,5 +659,195 @@ public async Task SumWithDecimalsAndArguments(string filterArgument, decimal exp
659659

660660
Assert.Equal(expectedValue, result.ToNumberValue());
661661
}
662+
663+
[Fact]
664+
public async Task Reject()
665+
{
666+
var input = new ArrayValue(new[] {
667+
new ObjectValue(new { Title = "a", Pinned = true }),
668+
new ObjectValue(new { Title = "b", Pinned = false }),
669+
new ObjectValue(new { Title = "c", Pinned = true })
670+
});
671+
672+
var options = new TemplateOptions();
673+
var context = new TemplateContext(options);
674+
options.MemberAccessStrategy.Register(new { Title = "a", Pinned = true }.GetType());
675+
676+
var arguments1 = new FilterArguments().Add(new StringValue("Pinned"));
677+
678+
var result1 = await ArrayFilters.Reject(input, arguments1, context);
679+
680+
Assert.Single(result1.Enumerate(context));
681+
682+
var arguments2 = new FilterArguments()
683+
.Add(new StringValue("Pinned"))
684+
.Add(BooleanValue.Create(false))
685+
;
686+
687+
var result2 = await ArrayFilters.Reject(input, arguments2, context);
688+
689+
Assert.Equal(2, result2.Enumerate(context).Count());
690+
691+
var arguments3 = new FilterArguments()
692+
.Add(new StringValue("Title"))
693+
.Add(new StringValue("c"));
694+
695+
var result3 = await ArrayFilters.Reject(input, arguments3, context);
696+
697+
Assert.Equal(2, result3.Enumerate(context).Count());
698+
}
699+
700+
[Fact]
701+
public async Task Find()
702+
{
703+
var input = new ArrayValue(new[] {
704+
new ObjectValue(new { Title = "a", Pinned = true }),
705+
new ObjectValue(new { Title = "b", Pinned = false }),
706+
new ObjectValue(new { Title = "c", Pinned = true })
707+
});
708+
709+
var options = new TemplateOptions();
710+
var context = new TemplateContext(options);
711+
options.MemberAccessStrategy.Register(new { Title = "a", Pinned = true }.GetType());
712+
713+
var arguments1 = new FilterArguments().Add(new StringValue("Pinned")).Add(BooleanValue.True);
714+
715+
var result1 = await ArrayFilters.Find(input, arguments1, context);
716+
717+
Assert.Equal(input.Values[0], result1);
718+
719+
var arguments2 = new FilterArguments()
720+
.Add(new StringValue("Pinned"))
721+
.Add(BooleanValue.Create(false))
722+
;
723+
724+
var result2 = await ArrayFilters.Find(input, arguments2, context);
725+
726+
Assert.Equal(input.Values[1], result2);
727+
728+
var arguments3 = new FilterArguments()
729+
.Add(new StringValue("Title"))
730+
.Add(new StringValue("c"));
731+
732+
var result3 = await ArrayFilters.Find(input, arguments3, context);
733+
734+
Assert.Equal(input.Values[2], result3);
735+
736+
var arguments4 = new FilterArguments();
737+
738+
var result4 = await ArrayFilters.Find(input, arguments4, context);
739+
740+
Assert.Equal(NilValue.Instance, result4);
741+
742+
var arguments5 = new FilterArguments()
743+
.Add(new StringValue("Title"))
744+
.Add(new StringValue("d"));
745+
746+
var result5 = await ArrayFilters.Find(input, arguments5, context);
747+
748+
Assert.Equal(NilValue.Instance, result5);
749+
}
750+
751+
[Fact]
752+
public async Task FindIndex()
753+
{
754+
var input = new ArrayValue(new[] {
755+
new ObjectValue(new { Title = "a", Pinned = true }),
756+
new ObjectValue(new { Title = "b", Pinned = false }),
757+
new ObjectValue(new { Title = "c", Pinned = true })
758+
});
759+
760+
var options = new TemplateOptions();
761+
var context = new TemplateContext(options);
762+
options.MemberAccessStrategy.Register(new { Title = "a", Pinned = true }.GetType());
763+
764+
var arguments1 = new FilterArguments().Add(new StringValue("Pinned")).Add(BooleanValue.True);
765+
766+
var result1 = await ArrayFilters.FindIndex(input, arguments1, context);
767+
768+
Assert.Equal(0, result1.ToNumberValue());
769+
770+
var arguments2 = new FilterArguments()
771+
.Add(new StringValue("Pinned"))
772+
.Add(BooleanValue.Create(false))
773+
;
774+
775+
var result2 = await ArrayFilters.FindIndex(input, arguments2, context);
776+
777+
Assert.Equal(1, result2.ToNumberValue());
778+
779+
var arguments3 = new FilterArguments()
780+
.Add(new StringValue("Title"))
781+
.Add(new StringValue("c"));
782+
783+
var result3 = await ArrayFilters.FindIndex(input, arguments3, context);
784+
785+
Assert.Equal(2, result3.ToNumberValue());
786+
787+
var arguments4 = new FilterArguments();
788+
789+
var result4 = await ArrayFilters.FindIndex(input, arguments4, context);
790+
791+
Assert.Equal(NilValue.Instance, result4);
792+
793+
var arguments5 = new FilterArguments()
794+
.Add(new StringValue("Title"))
795+
.Add(new StringValue("d"));
796+
797+
var result5 = await ArrayFilters.FindIndex(input, arguments5, context);
798+
799+
Assert.Equal(NilValue.Instance, result5);
800+
}
801+
802+
[Fact]
803+
public async Task Has()
804+
{
805+
var input = new ArrayValue(new[] {
806+
new ObjectValue(new { Title = "a", Pinned = true }),
807+
new ObjectValue(new { Title = "b", Pinned = false }),
808+
new ObjectValue(new { Title = "c", Pinned = true })
809+
});
810+
811+
var options = new TemplateOptions();
812+
var context = new TemplateContext(options);
813+
options.MemberAccessStrategy.Register(new { Title = "a", Pinned = true }.GetType());
814+
815+
var arguments1 = new FilterArguments().Add(new StringValue("Pinned")).Add(BooleanValue.True);
816+
817+
var result1 = await ArrayFilters.Has(input, arguments1, context);
818+
819+
Assert.Equal(BooleanValue.True, result1);
820+
821+
var arguments2 = new FilterArguments()
822+
.Add(new StringValue("Pinned"))
823+
.Add(BooleanValue.Create(false))
824+
;
825+
826+
var result2 = await ArrayFilters.Has(input, arguments2, context);
827+
828+
Assert.Equal(BooleanValue.True, result2);
829+
830+
var arguments3 = new FilterArguments()
831+
.Add(new StringValue("Title"))
832+
.Add(new StringValue("c"));
833+
834+
var result3 = await ArrayFilters.Has(input, arguments3, context);
835+
836+
Assert.Equal(BooleanValue.True, result3);
837+
838+
var arguments4 = new FilterArguments();
839+
840+
var result4 = await ArrayFilters.Has(input, arguments4, context);
841+
842+
Assert.Equal(BooleanValue.False, result4);
843+
844+
var arguments5 = new FilterArguments()
845+
.Add(new StringValue("Title"))
846+
.Add(new StringValue("d"));
847+
848+
var result5 = await ArrayFilters.Has(input, arguments5, context);
849+
850+
Assert.Equal(BooleanValue.False, result5);
851+
}
662852
}
663853
}

Fluid/Filters/ArrayFilters.cs

+113-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Fluid.Values;
1+
using Fluid.Values;
22

33
namespace Fluid.Filters
44
{
@@ -18,6 +18,10 @@ public static FilterCollection WithArrayFilters(this FilterCollection filters)
1818
filters.AddFilter("uniq", Uniq);
1919
filters.AddFilter("where", Where);
2020
filters.AddFilter("sum", Sum);
21+
filters.AddFilter("find", Find);
22+
filters.AddFilter("find_index", FindIndex);
23+
filters.AddFilter("has", Has);
24+
filters.AddFilter("reject", Reject);
2125
return filters;
2226
}
2327

@@ -162,6 +166,114 @@ public static async ValueTask<FluidValue> Where(FluidValue input, FilterArgument
162166
return new ArrayValue(list);
163167
}
164168

169+
public static async ValueTask<FluidValue> Find(FluidValue input, FilterArguments arguments, TemplateContext context)
170+
{
171+
if (input.Type != FluidValues.Array)
172+
{
173+
return input;
174+
}
175+
176+
// First argument is the property name to match
177+
var member = arguments.At(0).ToStringValue();
178+
179+
// Second argument is the value to match
180+
var targetValue = arguments.At(1);
181+
if (targetValue.IsNil())
182+
{
183+
return NilValue.Instance;
184+
}
185+
186+
FluidValue result = NilValue.Instance;
187+
188+
foreach (var item in input.Enumerate(context))
189+
{
190+
var itemValue = await item.GetValueAsync(member, context);
191+
192+
if (targetValue.Equals(itemValue))
193+
{
194+
result = item;
195+
break;
196+
}
197+
}
198+
199+
return result;
200+
}
201+
202+
public static async ValueTask<FluidValue> FindIndex(FluidValue input, FilterArguments arguments, TemplateContext context)
203+
{
204+
if (input.Type != FluidValues.Array)
205+
{
206+
return input;
207+
}
208+
209+
// First argument is the property name to match
210+
var member = arguments.At(0).ToStringValue();
211+
212+
// Second argument is the value to match
213+
var targetValue = arguments.At(1);
214+
if (targetValue.IsNil())
215+
{
216+
return NilValue.Instance;
217+
}
218+
219+
FluidValue result = NilValue.Instance;
220+
var index = 0;
221+
222+
foreach (var item in input.Enumerate(context))
223+
{
224+
var itemValue = await item.GetValueAsync(member, context);
225+
226+
if (targetValue.Equals(itemValue))
227+
{
228+
result = NumberValue.Create(index);
229+
break;
230+
}
231+
232+
index++;
233+
}
234+
235+
return result;
236+
}
237+
238+
public static async ValueTask<FluidValue> Has(FluidValue input, FilterArguments arguments, TemplateContext context)
239+
{
240+
var result = await Find(input, arguments, context);
241+
242+
return result.Equals(NilValue.Instance) ? BooleanValue.False : BooleanValue.True;
243+
}
244+
245+
public static async ValueTask<FluidValue> Reject(FluidValue input, FilterArguments arguments, TemplateContext context)
246+
{
247+
if (input.Type != FluidValues.Array)
248+
{
249+
return input;
250+
}
251+
252+
// First argument is the property name to match
253+
var member = arguments.At(0).ToStringValue();
254+
255+
// Second argument is the value to not match, or 'true' if none is defined
256+
var targetValue = arguments.At(1);
257+
if (targetValue.IsNil())
258+
{
259+
targetValue = BooleanValue.True;
260+
}
261+
262+
var list = new List<FluidValue>();
263+
264+
foreach (var item in input.Enumerate(context))
265+
{
266+
var itemValue = await item.GetValueAsync(member, context);
267+
268+
if (!targetValue.Equals(itemValue))
269+
{
270+
list.Add(item);
271+
}
272+
}
273+
274+
return new ArrayValue(list);
275+
}
276+
165277
public static ValueTask<FluidValue> Size(FluidValue input, FilterArguments arguments, TemplateContext context)
166278
{
167279
return input.GetValueAsync("size", context);

0 commit comments

Comments
 (0)