@@ -83,6 +83,19 @@ impl<'a> FilterParameters<'a> {
83
83
) ) ;
84
84
}
85
85
86
+ if fields. more_than_one_keyword_group_parameter ( ) {
87
+ let grouped_keyword_fields = fields
88
+ . parameters
89
+ . iter ( )
90
+ . filter ( |parameter| parameter. is_keyword_group ( ) )
91
+ . collect :: < Vec < _ > > ( ) ;
92
+
93
+ return Err ( Error :: new_spanned (
94
+ grouped_keyword_fields. first ( ) ,
95
+ "Found more than one keyword_group parameter, this is not allowd." ,
96
+ ) ) ;
97
+ }
98
+
86
99
let name = ident;
87
100
let evaluated_name = Self :: parse_attrs ( attrs) ?
88
101
. unwrap_or_else ( || Ident :: new ( & format ! ( "Evaluated{}" , name) , Span :: call_site ( ) ) ) ;
@@ -115,6 +128,16 @@ impl<'a> FilterParametersFields<'a> {
115
128
. find ( |parameter| !parameter. is_optional ( ) )
116
129
}
117
130
131
+ /// Predicate that indicates the presence of more than one keyword group
132
+ /// argument
133
+ fn more_than_one_keyword_group_parameter ( & self ) -> bool {
134
+ self . parameters
135
+ . iter ( )
136
+ . filter ( |parameter| parameter. is_keyword_group ( ) )
137
+ . count ( )
138
+ > 1
139
+ }
140
+
118
141
/// Tries to create a new `FilterParametersFields` from the given `Fields`
119
142
fn from_fields ( fields : & ' a Fields ) -> Result < Self > {
120
143
match fields {
@@ -256,6 +279,11 @@ impl<'a> FilterParameter<'a> {
256
279
self . meta . mode == FilterParameterMode :: Keyword
257
280
}
258
281
282
+ /// Returns whether this is a keyword list field.
283
+ fn is_keyword_group ( & self ) -> bool {
284
+ self . meta . mode == FilterParameterMode :: KeywordGroup
285
+ }
286
+
259
287
/// Returns the name of this parameter in liquid.
260
288
///
261
289
/// That is, by default, the name of the field as a string. However,
@@ -279,13 +307,15 @@ impl<'a> ToTokens for FilterParameter<'a> {
279
307
enum FilterParameterMode {
280
308
Keyword ,
281
309
Positional ,
310
+ KeywordGroup ,
282
311
}
283
312
284
313
impl FromStr for FilterParameterMode {
285
314
type Err = String ;
286
315
fn from_str ( s : & str ) -> std:: result:: Result < Self , Self :: Err > {
287
316
match s {
288
317
"keyword" => Ok ( FilterParameterMode :: Keyword ) ,
318
+ "keyword_group" => Ok ( FilterParameterMode :: KeywordGroup ) ,
289
319
"positional" => Ok ( FilterParameterMode :: Positional ) ,
290
320
s => Err ( format ! (
291
321
"Expected either \" keyword\" or \" positional\" . Found \" {}\" ." ,
@@ -424,6 +454,15 @@ fn generate_construct_positional_field(
424
454
}
425
455
}
426
456
457
+ /// Generates the statement that assigns the keyword list argument.
458
+ fn generate_construct_keyword_group_field ( field : & FilterParameter < ' _ > ) -> TokenStream {
459
+ let name = & field. name ;
460
+
461
+ quote ! {
462
+ let #name = Expression :: with_object_literal( keyword_as_map) ;
463
+ }
464
+ }
465
+
427
466
/// Generates the statement that evaluates the `Expression`
428
467
fn generate_evaluate_field ( field : & FilterParameter < ' _ > ) -> TokenStream {
429
468
let name = & field. name ;
@@ -582,6 +621,13 @@ fn generate_impl_filter_parameters(filter_parameters: &FilterParameters<'_>) ->
582
621
. iter ( )
583
622
. filter ( |parameter| parameter. is_keyword ( ) ) ;
584
623
624
+ let keyword_group_fields = fields
625
+ . parameters
626
+ . iter ( )
627
+ . filter ( |parameter| parameter. is_keyword_group ( ) ) ;
628
+
629
+ let group_keyword_param_exists = keyword_group_fields. peekable ( ) . peek ( ) . is_some ( ) ;
630
+
585
631
let match_keyword_parameters_arms = fields
586
632
. parameters
587
633
. iter ( )
@@ -597,6 +643,55 @@ fn generate_impl_filter_parameters(filter_parameters: &FilterParameters<'_>) ->
597
643
quote ! { let #field = #field. ok_or_else( || :: liquid_core:: error:: Error :: with_msg( concat!( "Expected named argument `" , #liquid_name, "`" ) ) ) ?; }
598
644
} ) ;
599
645
646
+ let keyword_group_fields_handling_blocks = fields
647
+ . parameters
648
+ . iter ( )
649
+ . filter ( |parameter| parameter. is_keyword_group ( ) )
650
+ . map ( generate_construct_keyword_group_field)
651
+ . collect :: < Vec < _ > > ( ) ;
652
+
653
+ let keyword_not_found_in_params_block = if group_keyword_param_exists {
654
+ // If there is a parameter that indicates all keywords should be grouped
655
+ // in an object, we generate an empty matching arm to prevent an error from
656
+ // being returned when a parsed keyword argument is not defines as a param.
657
+ quote ! {
658
+ { }
659
+ }
660
+ } else {
661
+ // If there is no parameter that indicates all keywords should be grouped,
662
+ // an error is returned when a keyword argument is found but has not being
663
+ // declared.
664
+ quote ! {
665
+ {
666
+ return :: std:: result:: Result :: Err ( :: liquid_core:: error:: Error :: with_msg( format!( "Unexpected named argument `{}`" , keyword) ) )
667
+ }
668
+ }
669
+ } ;
670
+
671
+ let assign_grouped_keyword_block = if group_keyword_param_exists {
672
+ keyword_group_fields_handling_blocks
673
+ . first ( )
674
+ . unwrap ( )
675
+ . clone ( )
676
+ } else {
677
+ quote ! { }
678
+ } ;
679
+
680
+ let keywords_handling_block = quote ! {
681
+ let mut keyword_as_map: std:: collections:: HashMap <String , liquid_core:: runtime:: Expression > = std:: collections:: HashMap :: new( ) ;
682
+ #( let mut #keyword_fields = :: std:: option:: Option :: None ; ) *
683
+ #[ allow( clippy:: never_loop) ] // This is not obfuscating the code because it's generated by a macro
684
+ while let :: std:: option:: Option :: Some ( arg) = args. keyword. next( ) {
685
+ keyword_as_map. insert( arg. 0 . into( ) , arg. 1 . clone( ) ) ;
686
+ match arg. 0 {
687
+ #( #match_keyword_parameters_arms) *
688
+ keyword => #keyword_not_found_in_params_block
689
+ }
690
+ }
691
+ #assign_grouped_keyword_block
692
+ #( #unwrap_required_keyword_fields) *
693
+ } ;
694
+
600
695
quote ! {
601
696
impl <' a> :: liquid_core:: parser:: FilterParameters <' a> for #name {
602
697
type EvaluatedFilterParameters = #evaluated_name<' a>;
@@ -606,18 +701,10 @@ fn generate_impl_filter_parameters(filter_parameters: &FilterParameters<'_>) ->
606
701
if let :: std:: option:: Option :: Some ( arg) = args. positional. next( ) {
607
702
return :: std:: result:: Result :: Err ( #too_many_args) ;
608
703
}
609
-
610
- #( let mut #keyword_fields = :: std:: option:: Option :: None ; ) *
611
- #[ allow( clippy:: never_loop) ] // This is not obfuscating the code because it's generated by a macro
612
- while let :: std:: option:: Option :: Some ( arg) = args. keyword. next( ) {
613
- match arg. 0 {
614
- #( #match_keyword_parameters_arms) *
615
- keyword => return :: std:: result:: Result :: Err ( :: liquid_core:: error:: Error :: with_msg( format!( "Unexpected named argument `{}`" , keyword) ) ) ,
616
- }
617
- }
618
- #( #unwrap_required_keyword_fields) *
704
+ #keywords_handling_block
619
705
620
706
Ok ( #name { #comma_separated_field_names } )
707
+
621
708
}
622
709
623
710
fn evaluate( & ' a self , runtime: & ' a dyn :: liquid_core:: runtime:: Runtime ) -> :: liquid_core:: error:: Result <Self :: EvaluatedFilterParameters > {
@@ -692,6 +779,12 @@ fn generate_impl_reflection(filter_parameters: &FilterParameters<'_>) -> TokenSt
692
779
. filter ( |parameter| parameter. is_keyword ( ) )
693
780
. map ( generate_parameter_reflection) ;
694
781
782
+ let kwg_params_reflection = fields
783
+ . parameters
784
+ . iter ( )
785
+ . filter ( |parameter| parameter. is_keyword_group ( ) )
786
+ . map ( generate_parameter_reflection) ;
787
+
695
788
let pos_params_reflection = fields
696
789
. parameters
697
790
. iter ( )
@@ -707,6 +800,10 @@ fn generate_impl_reflection(filter_parameters: &FilterParameters<'_>) -> TokenSt
707
800
fn keyword_parameters( ) -> & ' static [ :: liquid_core:: parser:: ParameterReflection ] {
708
801
& [ #( #kw_params_reflection) * ]
709
802
}
803
+
804
+ fn keyword_group_parameters( ) -> & ' static [ :: liquid_core:: parser:: ParameterReflection ] {
805
+ & [ #( #kwg_params_reflection) * ]
806
+ }
710
807
}
711
808
}
712
809
}
0 commit comments