Hello Jura, >> The above interface leaves open a few questions: >> >> - How is nesting handled? Does \keys_set:nn within >> \keys_set_grouped:nnn respect groups? Does >> \keys_set_grouped:nnn within \keys_set_grouped:nnn >> work in a union or intersection way? > > I'd say, 'yes' to the first (that would be closest to what is done in > pgfkeys), and given that, 'intersection' to the second. I've been looking at this in more detail, and I suspect that sticking to the 'no nesting' position is easiest (other than \keys_set:nn within \keys_set_... respects the outer position: needed for .meta:n, etc.). I don't see a lot a real use cases for nesting, while I do see quite a bit of complexity in allowing it! >> - Do unknown keys raise an error, or are they ignored as they >> are not in any group? > > Following pgfkeys again, I think they should raise an error. See below. >> - Do unused options need to be collected/available? > > I've not had the occasion to use this myself, but pgfkeys does provide > it. One application that immediately pops to my mind is the control of > key-setting order when the order is important, which is potentially > rather useful. So why not have \keys_set_grouped:nnnN, allowing > something like: OK, I can build in the ability to do this. Worth bearing in mind that grouping is intended for the case where you want to have a 'second axis' for keys. If you always want to be able to set stuff in an order, then \keys_set_known:nnN plus subtrees already works. (We added this a while ago, primarily as Will needed it for fontspec.) > \cs_new_protected:Npn \__mymodule_keys_set_in_order_and_do_something:n #1 > Another question is what should happen to keys that haven't been > assigned a group. Right now, they're filtered out, but perhaps it > would be better to set them. In that way, if there are keys that > should never be filtered, they won't need to be assigned a fallback > group to accomplish this. Pgfkeys provides both options, but it seems > to me it's easier for package writers using l3keys to just follow the > principle that they should assign a key to a group only if they ever > want to filter it out. This ties in with one aspect of how the draft > \keys_set_groups:nnn behaves that I feel should be definitely changed. > Right now, when the second argument is empty, all the keys get set, > which is the opposite of what one would expect. I think, instead, no > keys should be set, except for those that have no group assigned to > them. I believe changing \tl_set:Nx \l__keys_groups_clist {#4} to > \tl_set:Nx \l__keys_groups_clist { , #4 } in the definition of > \__keys_set_grouped:nnnnn would do this. I get the feeling there are two related but distinct requirements here: - Set only keys from specified groups ('opt-in') - Set keys unless they are in groups specified to be filtered out ('opt-out') The following implements that, but note that you will need the 'burning edge' expl3 from GitHub/SVN as I've made some adjustments to the l3keys code to allow for things we certainly will want whatever the final form of groups/families. \cs_new_protected:Npn \__keys_groups_set:n #1 { \prop_if_exist:cT { \c__keys_info_root_tl \l_keys_path_tl } { \prop_put:cnn { \c__keys_info_root_tl \l_keys_path_tl } { groups } {#1} } } \cs_new_protected:cpn { \c__keys_props_root_tl .groups:n } #1 { \__keys_groups_set:n {#1} } \bool_new:N \l__keys_filter_bool \bool_new:N \l__keys_groups_bool \seq_new:N \l__keys_groups_seq \tl_new:N \l__keys_groups_tl \bool_new:N \l__keys_tmp_bool \cs_new_protected:Npn \keys_set_filter:nnn #1#2#3 { \bool_set_true:N \l__keys_filter_bool \seq_set_from_clist:Nn \l__keys_groups_seq {#2} \keys_set:nn {#1} {#3} \bool_set_false:N \l__keys_filter_bool } \cs_new_protected:Npn \keys_set_filter:nnnN #1#2#3#4 { \clist_clear:N \l__keys_unused_clist \keys_set_filter:nn {#1} {#2} {#3} \tl_set:Nx #3 { \exp_not:o { \l__keys_unused_clist } } } \cs_new_protected:Npn \keys_set_groups:nnn #1#2#3 { \bool_set_true:N \l__keys_groups_bool \seq_set_from_clist:Nn \l__keys_groups_seq {#2} \keys_set:nn {#1} {#3} \bool_set_false:N \l__keys_groups_bool } \cs_set_protected:Npn \__keys_set_elt_aux:nn #1#2 { \tl_set:Nx \l_keys_key_tl { \tl_to_str:n {#1} } \tl_set:Nx \l_keys_path_tl { \l__keys_module_tl / \l_keys_key_tl } \__keys_value_or_default:n {#2} \bool_if:nTF { \l__keys_filter_bool || \l__keys_groups_bool } { \__keys_set_elt_grouped: } { \__keys_set_elt_aux: } } \cs_new_protected_nopar:Npn \__keys_set_elt_aux: { \bool_if:nTF { \__keys_if_value_p:n { required } && \l__keys_no_value_bool } { \__msg_kernel_error:nnx { kernel } { value-required } { \l_keys_path_tl } } { \bool_if:nTF { \__keys_if_value_p:n { forbidden } && ! \l__keys_no_value_bool } { \__msg_kernel_error:nnxx { kernel } { value-forbidden } { \l_keys_path_tl } { \l_keys_value_tl } } { \__keys_execute: } } } \cs_new_protected_nopar:Npn \__keys_set_elt_grouped: { \prop_if_exist:cTF { \c__keys_info_root_tl \l_keys_path_tl } { \prop_get:cnNTF { \c__keys_info_root_tl \l_keys_path_tl } { groups } \l__keys_groups_tl { \__keys_check_groups: } { \bool_if:NTF \l__keys_filter_bool { \__keys_set_elt_aux: } { \__keys_store_unused: } } } { \bool_if:NTF \l__keys_filter_bool { \__keys_set_elt_aux: } { \__keys_store_unused: } } } \cs_new_protected_nopar:Npn \__keys_store_unused: { \clist_put_right:Nx \l__keys_unknown_clist { \exp_not:o \l_keys_key_tl \bool_if:NF \l__keys_no_value_bool { = { \exp_not:o \l_keys_value_tl } } } } \cs_new_protected_nopar:Npn \__keys_check_groups: { \bool_set_false:N \l__keys_tmp_bool \seq_map_inline:Nn \l__keys_groups_seq { \clist_map_inline:Vn \l__keys_groups_tl { \str_if_eq:nnT {##1} {####1} { \bool_set_true:N \l__keys_tmp_bool \clist_map_break:n { \seq_map_break: } } } } \bool_if:NTF \l__keys_tmp_bool { \bool_if:NTF \l__keys_filter_bool { \__keys_store_unused: } { \__keys_set_elt_aux: } } { \bool_if:NTF \l__keys_filter_bool { \__keys_set_elt_aux: } { \__keys_store_unused: } } } \cs_generate_variant:Nn \clist_map_inline:nn { V } \keys_define:nn { module } { key-one .code:n = { \tl_show:n { key-one } }, key-one .groups:n = { a , b }, key-two .code:n = { \tl_show:n { SAW:~key-two } }, key-two .groups:n = { c , f }, } \keys_set_groups:nnn { module } { a , c } { key-one = value , key-two = value-test , key-odd = value } -- Joseph Wright