> Looking at this, both Bruno and I noticed that if you are allowing a > clist then in principal you could simply extend \keys_set:nn to have a > list for the first argument. However, I'm not sure that's quite right. > > If you look at the pgfkeys demo above, what you see is that all of the > keys are in the same path: > > /mymodule/key-A-i > /mymodule/key-B-i > > with the family as a 'secondary path' (or something like that). This > means three things: > > 1) With filtering turned 'off', the keys can all be set in one > operation (no need to try different places) > > 2) The key names are unique > > 3) If the key is not found, there is one place to look for an > unknown handler: /mymodule/unknown > > On the other hand, the proposed extension to l3keys uses key path for > filtering, so we find in contrast > > 1) The keys can only all be set by actively choosing each subgroup > > 2) Key names may not be unique, so the order of subgroups becomes > important > > 3) Handling for unknown keys is far from clear (do we look in > /mymodule/, /mymodule/subgroup-A/, ...?) > > I'm going to take a look at how pgfkeys actually handles this. > -- > Joseph Wright Yes, you're right, of course! The pgfkeys family structure is independent of the tree structure, and I can't remember now why I tried doing it based on the paths... The problems you bring up really should have been obvious to me. :S Anyhow, here is a version (appended below) that does follow the pgfkeys approach more closely. I've not tested it very thoroughly, but it seems to work... Again, I'm sure there's much room for improvement! There are two new properties: 1) .filter: - defines a key as a filter 2) .filters:n - takes a clist argument that specifies which filters are to be applied to a key (this is unlike the pgfkeys '.belongs to family' handler, which only accepts a single family key) And there are several new public commands: 1) \keys_filters_activate:nn {<module>} {<filter clist>} 2) \keys_filters_deactivate:nn {<module>} {<filter clist>} 3) \keys_filters_deactivate_all: 4) \keys_set_filtered:nn {<module>} {<keyval list>} 5) \keys_set_known_filtered:nnN {<module>} {<keyval list>} <clist> and a couple of public variables \l_keys_filtered_seq - which contains the keys that were filtered out on the last call of \keys_set_filtered:nn \l_active_filters_seq - which contains the filter keys currently activated A key will be filtered out if any of the filters that apply to it are active, and keys with no filters applied will be set normally. \bool_new:N \l__key_filtered_bool \clist_new:N \l__key_filters_clist \prop_new:N \l__filter_states_prop \prop_new:N \l__keys_filters_prop \seq_new:N \l_keys_filtered_seq \seq_new:N \l_active_filters_seq \seq_new:N \l__active_filters_seq \cs_generate_variant:Nn \__msg_kernel_error:nnnnn { nnoVV } \cs_generate_variant:Nn \keys_define:nn { Vo } \cs_generate_variant:Nn \keys_if_exist:nnTF { VnTF } \cs_generate_variant:Nn \keys_set:nn { Vn } \cs_generate_variant:Nn \keys_set_known:nnN { VnN } \cs_generate_variant:Nn \prop_get:NnN { NxN } \cs_generate_variant:Nn \tl_if_eq:nnT { VnT } \cs_new_protected:Npn \__keys_filter_define: { \prop_if_in:NnF \l__filter_states_prop { \l_keys_path_tl } { \prop_put:NVn \l__filter_states_prop \l_keys_path_tl { 0 } } } \cs_new_protected:Npn \__keys_filters_set:n #1 { \clist_clear:N \l__key_filters_clist \clist_map_inline:nn {#1} { \prop_if_in:NoTF \l__filter_states_prop { \l__keys_module_tl / ##1 } { \clist_put_right:No \l__key_filters_clist { \l__keys_module_tl / ##1 } } { \__msg_kernel_error:nnoVV { kernel } { invalid-filter } { \l__keys_module_tl / ##1 } \l_keys_path_tl \l__keys_module_tl } } \clist_if_empty:NF \l__valid_filters_clist { \prop_put:NVV \l__keys_filters_prop \l_keys_path_tl \l__key_filters_clist } } \cs_new_protected:cpn { \c__keys_props_root_tl .filter: } { \__keys_filter_define: } \cs_new_protected:cpn { \c__keys_props_root_tl .filters:n } #1 { \__keys_filters_set:n {#1} } \cs_new_protected:Npn \keys_filters_activate:nn #1#2 { \clist_map_inline:nn {#2} { \prop_if_in:NnTF \l__filter_states_prop { #1 / ##1 } { \prop_put:Nnn \l__filter_states_prop { #1 / ##1 } { 1 } \seq_put_right:Nn \l__active_filters_seq { #1 / ##1 } } { \__msg_kernel_error:nnnn { kernel } { unknown-filter } { #1 / ##1 } {#1} } } \seq_set_eq:NN \l_active_filters_seq \l__active_filters_seq } \cs_new_protected:Npn \keys_filters_deactivate:nn #1#2 { \clist_map_inline:nn {#2} { \prop_if_in:NnTF \l__filter_states_prop { #1 / ##1 } { \prop_put:Nnn \l__filter_states_prop { #1 / ##1 } { 0 } \seq_remove_all:Nn \l__active_filters_seq { #1 / ##1 } } { \__msg_kernel_error:nnnn { kernel } { unknown-filter } { #1 / ##1 } {#1} } } \seq_set_eq:NN \l_active_filters_seq \l__active_filters_seq } \cs_new_protected:Npn \keys_filters_deactivate_all: { \seq_map_inline:Nn \l__active_filters_seq { \prop_put:Nnn \l__filter_states_prop {##1} { 0 } } \seq_clear:N \l__active_filters_seq \seq_set_eq:NN \l_active_filters_seq \l__active_filters_seq } \cs_new_protected:Npn \keys_set_filtered:nn #1#2 { \seq_clear:N \l_keys_filtered_seq \tl_set:Nn \l__keys_module_tl {#1} \keyval_parse:NNn \__keys_set_filtered:n \__keys_set_filtered:nn {#2} } \cs_new_protected:Npn \__keys_set_filtered:n #1 { \prop_get:NoNTF \l__keys_filters_prop { \l__keys_module_tl / #1 } \l__key_filters_clist { \bool_set_false:N \l__key_filtered_bool \clist_map_inline:Nn \l__key_filters_clist { \prop_get:NnN \l__filter_states_prop {##1} \l__tmpa_tl \tl_if_eq:VnT \l__tmpa_tl { 1 } { \bool_set_true:N \l__key_filtered_bool \clist_map_break: } } \bool_if:NTF \l__key_filtered_bool { \seq_put_right:Nn \l_keys_filtered_seq {#1} } { \keys_set:Vn \l__keys_module_tl {#1} } } { \keys_set:Vn \l__keys_module_tl {#1} } } \cs_new_protected:Npn \__keys_set_filtered:nn #1#2 { \prop_get:NoNTF \l__keys_filters_prop { \l__keys_module_tl / #1 } \l__key_filters_clist { \bool_set_false:N \l__key_filtered_bool \clist_map_inline:Nn \l__key_filters_clist { \prop_get:NnN \l__filter_states_prop {##1} \l__tmpa_tl \tl_if_eq:VnT \l__tmpa_tl { 1 } { \bool_set_true:N \l__key_filtered_bool \clist_map_break: } } \bool_if:NTF \l__key_filtered_bool { \seq_put_right:Nn \l_keys_filtered_seq { #1 = #2 } } { \keys_set:Vn \l__keys_module_tl { #1 = #2 } } } { \keys_set:Vn \l__keys_module_tl { #1 = #2 } } } \cs_new_protected:Npn \keys_set_known_filtered:nnN #1#2#3 { \seq_clear:N \l_keys_filtered_seq \clist_clear:N \l__tmpa_clist \clist_clear:N \l__tmpb_clist \tl_set:Nn \l__keys_module_tl {#1} \keyval_parse:NNn \__keys_set_known_filtered:n \__keys_set_known_filtered:nn {#2} \clist_concat:NNN #3 \l__tmpa_clist \l__tmpb_clist } \cs_new_protected:Npn \__keys_set_known_filtered:n #1 { \prop_get:NoNTF \l__keys_filters_prop { \l__keys_module_tl / #1 } \l__key_filters_clist { \bool_set_false:N \l__key_filtered_bool \clist_map_inline:Nn \l__key_filters_clist { \prop_get:NnN \l__filter_states_prop {##1} \l__tmpa_tl \tl_if_eq:VnT \l__tmpa_tl { 1 } { \bool_set_true:N \l__key_filtered_bool \clist_map_break: } } \bool_if:NTF \l__key_filtered_bool { \seq_put_right:Nn \l_keys_filtered_seq {#1} } { \keys_set_known:VnN \l__keys_module_tl {#1} \l__tmpa_clist } } { \keys_set_known:VnN \l__keys_module_tl {#1} \l__tmpa_clist } } \cs_new_protected:Npn \__keys_set_known_filtered:nn #1#2 { \prop_get:NoNTF \l__keys_filters_prop { \l__keys_module_tl / #1 } \l__key_filters_clist { \bool_set_false:N \l__key_filtered_bool \clist_map_inline:Nn \l__key_filters_clist { \prop_get:NnN \l__filter_states_prop {##1} \l__tmpa_tl \tl_if_eq:VnT \l__tmpa_tl { 1 } { \bool_set_true:N \l__key_filtered_bool \clist_map_break: } } \bool_if:NTF \l__key_filtered_bool { \seq_put_right:Nn \l_keys_filtered_seq { #1 = #2 } } { \keys_set_known:VnN \l__keys_module_tl { #1 = #2 } \l__tmpb_clist } } { \keys_set_known:VnN \l__keys_module_tl { #1 = #2 } \l__tmpb_clist } } \__msg_kernel_new:nnnn { kernel } { invalid-filter } { The~filter~'#1'~does~not~exist~and~will~not~be~applied~to~key~'#2'. } { Filter~’#1’~has~not~been~defined~for~the~module~'#3'.\\ A~filter~must~be~defined~using~the~.filter:~property~ before~it~can~be~applied~to~any~keys. } \__msg_kernel_new:nnnn { kernel } { unknown-filter } { The~filter~'#1'~is~not~defined.~It~cannot~be~activated~or~deactivated. } { Filter~’#1’~has~not~been~defined~for~the~module~'#2'.\\ A~filter~must~be~defined~using~the~.filter:~property~ before~it~can~be~applied. } By the way, the syntax and function of my initially proposed \keys_subgroups_set:nnn could be roughly captured by the following code (though I doubt it would add anything useful any more) \cs_new_protected:Npn \keys_subgroups_set:nnn #1#2#3 { \prop_set_eq:NN \l__tmpa_prop \l__filter_states_prop \keys_filters_deactivate_all: \keys_filters_activate:nn {#1} {#2} \keys_set_filtered:nn {#1} {#3} \prop_set_eq:NN \l__filter_states_prop \l__tmpa_prop } \cs_new_protected:Npn \keys_subgroups_set_known:nnn #1#2#3 { \prop_set_eq:NN \l__tmpa_prop \l__filter_states_prop \keys_filters_deactivate_all: \keys_filters_activate:nn {#1} {#2} \keys_set_known_filtered:nn {#1} {#3} \prop_set_eq:NN \l__filter_states_prop \l__tmpa_prop }