diff --git a/.github/workflows/module_cargo_will_push.yml b/.github/workflows/module_cargo_will_push.yml index 21f10e3bf2..302282174f 100644 --- a/.github/workflows/module_cargo_will_push.yml +++ b/.github/workflows/module_cargo_will_push.yml @@ -2,7 +2,7 @@ name : cargo_will on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_clone_dyn_meta_push.yml b/.github/workflows/module_clone_dyn_meta_push.yml index c7bba8273c..d9d94012f5 100644 --- a/.github/workflows/module_clone_dyn_meta_push.yml +++ b/.github/workflows/module_clone_dyn_meta_push.yml @@ -2,7 +2,7 @@ name : clone_dyn_meta on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_clone_dyn_push.yml b/.github/workflows/module_clone_dyn_push.yml index c317d7a905..d7b480578b 100644 --- a/.github/workflows/module_clone_dyn_push.yml +++ b/.github/workflows/module_clone_dyn_push.yml @@ -2,7 +2,7 @@ name : clone_dyn on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_collection_tools_push.yml b/.github/workflows/module_collection_tools_push.yml index b15ad33e2d..d1325627e2 100644 --- a/.github/workflows/module_collection_tools_push.yml +++ b/.github/workflows/module_collection_tools_push.yml @@ -2,7 +2,7 @@ name : collection_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_crates_tools_push.yml b/.github/workflows/module_crates_tools_push.yml index 7584d03db9..1b06311c6c 100644 --- a/.github/workflows/module_crates_tools_push.yml +++ b/.github/workflows/module_crates_tools_push.yml @@ -2,7 +2,7 @@ name : crates_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_data_type_push.yml b/.github/workflows/module_data_type_push.yml index f97d698bf7..64fa876fc6 100644 --- a/.github/workflows/module_data_type_push.yml +++ b/.github/workflows/module_data_type_push.yml @@ -2,7 +2,7 @@ name : data_type on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_derive_tools_meta_push.yml b/.github/workflows/module_derive_tools_meta_push.yml index 66b6bae97f..4bbc62af5a 100644 --- a/.github/workflows/module_derive_tools_meta_push.yml +++ b/.github/workflows/module_derive_tools_meta_push.yml @@ -2,7 +2,7 @@ name : derive_tools_meta on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_derive_tools_push.yml b/.github/workflows/module_derive_tools_push.yml index 90eaa4abaf..723b900d2d 100644 --- a/.github/workflows/module_derive_tools_push.yml +++ b/.github/workflows/module_derive_tools_push.yml @@ -2,7 +2,7 @@ name : derive_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_deterministic_rand_push.yml b/.github/workflows/module_deterministic_rand_push.yml index 551a13100b..a761d7cb83 100644 --- a/.github/workflows/module_deterministic_rand_push.yml +++ b/.github/workflows/module_deterministic_rand_push.yml @@ -2,7 +2,7 @@ name : deterministic_rand on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_diagnostics_tools_push.yml b/.github/workflows/module_diagnostics_tools_push.yml index f9b78da3ad..cdb337ebd1 100644 --- a/.github/workflows/module_diagnostics_tools_push.yml +++ b/.github/workflows/module_diagnostics_tools_push.yml @@ -2,7 +2,7 @@ name : diagnostics_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_error_tools_push.yml b/.github/workflows/module_error_tools_push.yml index 80c57d84d3..39c342f339 100644 --- a/.github/workflows/module_error_tools_push.yml +++ b/.github/workflows/module_error_tools_push.yml @@ -2,7 +2,7 @@ name : error_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_exe_tools_push.yml b/.github/workflows/module_exe_tools_push.yml index e1ae566533..f340a4bbd6 100644 --- a/.github/workflows/module_exe_tools_push.yml +++ b/.github/workflows/module_exe_tools_push.yml @@ -2,7 +2,7 @@ name : exe_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_file_tools_push.yml b/.github/workflows/module_file_tools_push.yml index 21d3f0b973..f6bc6e7ae6 100644 --- a/.github/workflows/module_file_tools_push.yml +++ b/.github/workflows/module_file_tools_push.yml @@ -2,7 +2,7 @@ name : file_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_for_each_push.yml b/.github/workflows/module_for_each_push.yml index c6ee0c63fb..6b8fb841df 100644 --- a/.github/workflows/module_for_each_push.yml +++ b/.github/workflows/module_for_each_push.yml @@ -2,7 +2,7 @@ name : for_each on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_former_meta_push.yml b/.github/workflows/module_former_meta_push.yml index ef166669ca..48d9ab029c 100644 --- a/.github/workflows/module_former_meta_push.yml +++ b/.github/workflows/module_former_meta_push.yml @@ -2,7 +2,7 @@ name : former_meta on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_former_push.yml b/.github/workflows/module_former_push.yml index 33439f4ffe..f5757ce568 100644 --- a/.github/workflows/module_former_push.yml +++ b/.github/workflows/module_former_push.yml @@ -2,7 +2,7 @@ name : former on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_fs_tools_push.yml b/.github/workflows/module_fs_tools_push.yml index 31d4f31aba..a0a5933d00 100644 --- a/.github/workflows/module_fs_tools_push.yml +++ b/.github/workflows/module_fs_tools_push.yml @@ -2,7 +2,7 @@ name : fs_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_fundamental_data_type_push.yml b/.github/workflows/module_fundamental_data_type_push.yml index 5d22e3e2c7..e1b249f10a 100644 --- a/.github/workflows/module_fundamental_data_type_push.yml +++ b/.github/workflows/module_fundamental_data_type_push.yml @@ -2,7 +2,7 @@ name : fundamental_data_type on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_graphs_tools_push.yml b/.github/workflows/module_graphs_tools_push.yml index e455ddff27..171b256c36 100644 --- a/.github/workflows/module_graphs_tools_push.yml +++ b/.github/workflows/module_graphs_tools_push.yml @@ -2,7 +2,7 @@ name : graphs_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_image_tools_push.yml b/.github/workflows/module_image_tools_push.yml index 4e14f26d7f..ca8bc1059c 100644 --- a/.github/workflows/module_image_tools_push.yml +++ b/.github/workflows/module_image_tools_push.yml @@ -2,7 +2,7 @@ name : image_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_implements_push.yml b/.github/workflows/module_implements_push.yml index 2e5705e763..76efd0b848 100644 --- a/.github/workflows/module_implements_push.yml +++ b/.github/workflows/module_implements_push.yml @@ -2,7 +2,7 @@ name : implements on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_impls_index_meta_push.yml b/.github/workflows/module_impls_index_meta_push.yml index 0b48e4a551..da1fc0c8bd 100644 --- a/.github/workflows/module_impls_index_meta_push.yml +++ b/.github/workflows/module_impls_index_meta_push.yml @@ -2,7 +2,7 @@ name : impls_index_meta on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_impls_index_push.yml b/.github/workflows/module_impls_index_push.yml index 461b3073e6..bffccc6c8e 100644 --- a/.github/workflows/module_impls_index_push.yml +++ b/.github/workflows/module_impls_index_push.yml @@ -2,7 +2,7 @@ name : impls_index on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_include_md_push.yml b/.github/workflows/module_include_md_push.yml index 8c18229ca9..d48608d38e 100644 --- a/.github/workflows/module_include_md_push.yml +++ b/.github/workflows/module_include_md_push.yml @@ -2,7 +2,7 @@ name : include_md on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_inspect_type_push.yml b/.github/workflows/module_inspect_type_push.yml index 4c5052e2d5..79f7219395 100644 --- a/.github/workflows/module_inspect_type_push.yml +++ b/.github/workflows/module_inspect_type_push.yml @@ -2,7 +2,7 @@ name : inspect_type on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_instance_of_push.yml b/.github/workflows/module_instance_of_push.yml index c8854f4644..52403c3004 100644 --- a/.github/workflows/module_instance_of_push.yml +++ b/.github/workflows/module_instance_of_push.yml @@ -2,7 +2,7 @@ name : instance_of on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_interval_adapter_push.yml b/.github/workflows/module_interval_adapter_push.yml index af32acd608..f720644edc 100644 --- a/.github/workflows/module_interval_adapter_push.yml +++ b/.github/workflows/module_interval_adapter_push.yml @@ -2,7 +2,7 @@ name : interval_adapter on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_is_slice_push.yml b/.github/workflows/module_is_slice_push.yml index ea170197e3..469ad60f45 100644 --- a/.github/workflows/module_is_slice_push.yml +++ b/.github/workflows/module_is_slice_push.yml @@ -2,7 +2,7 @@ name : is_slice on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_iter_tools_push.yml b/.github/workflows/module_iter_tools_push.yml index 01a24dcffa..e309b039bb 100644 --- a/.github/workflows/module_iter_tools_push.yml +++ b/.github/workflows/module_iter_tools_push.yml @@ -2,7 +2,7 @@ name : iter_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_macro_tools_push.yml b/.github/workflows/module_macro_tools_push.yml index 99f51d2309..b6bfa53f2c 100644 --- a/.github/workflows/module_macro_tools_push.yml +++ b/.github/workflows/module_macro_tools_push.yml @@ -2,7 +2,7 @@ name : macro_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_math_tools_push.yml b/.github/workflows/module_math_tools_push.yml index d113b1b198..07e965fbf1 100644 --- a/.github/workflows/module_math_tools_push.yml +++ b/.github/workflows/module_math_tools_push.yml @@ -2,7 +2,7 @@ name : math_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_mem_tools_push.yml b/.github/workflows/module_mem_tools_push.yml index abfb7b259f..acca4cac93 100644 --- a/.github/workflows/module_mem_tools_push.yml +++ b/.github/workflows/module_mem_tools_push.yml @@ -2,7 +2,7 @@ name : mem_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_meta_tools_push.yml b/.github/workflows/module_meta_tools_push.yml index 6ab35166ff..8aacc2691f 100644 --- a/.github/workflows/module_meta_tools_push.yml +++ b/.github/workflows/module_meta_tools_push.yml @@ -2,7 +2,7 @@ name : meta_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_mod_interface_meta_push.yml b/.github/workflows/module_mod_interface_meta_push.yml index a66cafb922..c7a1ed7477 100644 --- a/.github/workflows/module_mod_interface_meta_push.yml +++ b/.github/workflows/module_mod_interface_meta_push.yml @@ -2,7 +2,7 @@ name : mod_interface_meta on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_mod_interface_push.yml b/.github/workflows/module_mod_interface_push.yml index ea0077a7bc..2f0bd2f75a 100644 --- a/.github/workflows/module_mod_interface_push.yml +++ b/.github/workflows/module_mod_interface_push.yml @@ -2,7 +2,7 @@ name : mod_interface on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_multilayer_push.yml b/.github/workflows/module_multilayer_push.yml index c0651726fe..a2929d01d9 100644 --- a/.github/workflows/module_multilayer_push.yml +++ b/.github/workflows/module_multilayer_push.yml @@ -2,7 +2,7 @@ name : multilayer on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_optimization_tools_push.yml b/.github/workflows/module_optimization_tools_push.yml index 5083841c3f..8a1c8cf89d 100644 --- a/.github/workflows/module_optimization_tools_push.yml +++ b/.github/workflows/module_optimization_tools_push.yml @@ -2,7 +2,7 @@ name : optimization_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_plot_interface_push.yml b/.github/workflows/module_plot_interface_push.yml index 8a1bb71727..22ed8a9c72 100644 --- a/.github/workflows/module_plot_interface_push.yml +++ b/.github/workflows/module_plot_interface_push.yml @@ -2,7 +2,7 @@ name : plot_interface on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_proc_macro_tools_push.yml b/.github/workflows/module_proc_macro_tools_push.yml index 936b353d90..d9aafa1cbb 100644 --- a/.github/workflows/module_proc_macro_tools_push.yml +++ b/.github/workflows/module_proc_macro_tools_push.yml @@ -2,7 +2,7 @@ name : proc_macro_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_process_tools_push.yml b/.github/workflows/module_process_tools_push.yml index 895457b3b9..217cc648de 100644 --- a/.github/workflows/module_process_tools_push.yml +++ b/.github/workflows/module_process_tools_push.yml @@ -2,7 +2,7 @@ name : process_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_proper_path_tools_push.yml b/.github/workflows/module_proper_path_tools_push.yml index fcff1bb334..e7adc2bbb2 100644 --- a/.github/workflows/module_proper_path_tools_push.yml +++ b/.github/workflows/module_proper_path_tools_push.yml @@ -2,7 +2,7 @@ name : proper_path_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_proper_tools_push.yml b/.github/workflows/module_proper_tools_push.yml index 7e1ff42c3e..798d19b582 100644 --- a/.github/workflows/module_proper_tools_push.yml +++ b/.github/workflows/module_proper_tools_push.yml @@ -2,7 +2,7 @@ name : proper_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_refiner_push.yml b/.github/workflows/module_refiner_push.yml index d05c5b7721..e48de2c4ec 100644 --- a/.github/workflows/module_refiner_push.yml +++ b/.github/workflows/module_refiner_push.yml @@ -2,7 +2,7 @@ name : refiner on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_reflect_tools_meta_push.yml b/.github/workflows/module_reflect_tools_meta_push.yml index a799d1203c..d8319d1eaa 100644 --- a/.github/workflows/module_reflect_tools_meta_push.yml +++ b/.github/workflows/module_reflect_tools_meta_push.yml @@ -2,7 +2,7 @@ name : reflect_tools_meta on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_reflect_tools_push.yml b/.github/workflows/module_reflect_tools_push.yml index 8cd20cba88..7384babc65 100644 --- a/.github/workflows/module_reflect_tools_push.yml +++ b/.github/workflows/module_reflect_tools_push.yml @@ -2,7 +2,7 @@ name : reflect_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_sqlx_query_push.yml b/.github/workflows/module_sqlx_query_push.yml index 8971a72e9c..896a0ebcbd 100644 --- a/.github/workflows/module_sqlx_query_push.yml +++ b/.github/workflows/module_sqlx_query_push.yml @@ -2,7 +2,7 @@ name : sqlx_query on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_strs_tools_push.yml b/.github/workflows/module_strs_tools_push.yml index c78284be32..b62f5d0ff6 100644 --- a/.github/workflows/module_strs_tools_push.yml +++ b/.github/workflows/module_strs_tools_push.yml @@ -2,7 +2,7 @@ name : strs_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_test_experimental_a_push.yml b/.github/workflows/module_test_experimental_a_push.yml index d1c9875c6e..3b8f21e98a 100644 --- a/.github/workflows/module_test_experimental_a_push.yml +++ b/.github/workflows/module_test_experimental_a_push.yml @@ -2,7 +2,7 @@ name : test_experimental_a on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_test_experimental_b_push.yml b/.github/workflows/module_test_experimental_b_push.yml index 64273ba81a..7eb23049bc 100644 --- a/.github/workflows/module_test_experimental_b_push.yml +++ b/.github/workflows/module_test_experimental_b_push.yml @@ -2,7 +2,7 @@ name : test_experimental_b on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_test_experimental_c_push.yml b/.github/workflows/module_test_experimental_c_push.yml index 6357d5a962..8d44763090 100644 --- a/.github/workflows/module_test_experimental_c_push.yml +++ b/.github/workflows/module_test_experimental_c_push.yml @@ -2,7 +2,7 @@ name : test_experimental_c on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_test_tools_push.yml b/.github/workflows/module_test_tools_push.yml index 9a474a75b8..d75629dc41 100644 --- a/.github/workflows/module_test_tools_push.yml +++ b/.github/workflows/module_test_tools_push.yml @@ -2,7 +2,7 @@ name : test_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_time_tools_push.yml b/.github/workflows/module_time_tools_push.yml index fb7cbaf00d..49d89bb2e9 100644 --- a/.github/workflows/module_time_tools_push.yml +++ b/.github/workflows/module_time_tools_push.yml @@ -2,7 +2,7 @@ name : time_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_typing_tools_push.yml b/.github/workflows/module_typing_tools_push.yml index dadb561f77..c3e0040579 100644 --- a/.github/workflows/module_typing_tools_push.yml +++ b/.github/workflows/module_typing_tools_push.yml @@ -2,7 +2,7 @@ name : typing_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_unitore_push.yml b/.github/workflows/module_unitore_push.yml index f783dfab5a..1dca0c370d 100644 --- a/.github/workflows/module_unitore_push.yml +++ b/.github/workflows/module_unitore_push.yml @@ -2,7 +2,7 @@ name : unitore on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_variadic_from_push.yml b/.github/workflows/module_variadic_from_push.yml index eee1477a41..c147ba0aa5 100644 --- a/.github/workflows/module_variadic_from_push.yml +++ b/.github/workflows/module_variadic_from_push.yml @@ -2,7 +2,7 @@ name : variadic_from on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_w_4_d_push.yml b/.github/workflows/module_w_4_d_push.yml index 219c5fd8e5..3f8ff05a50 100644 --- a/.github/workflows/module_w_4_d_push.yml +++ b/.github/workflows/module_w_4_d_push.yml @@ -2,7 +2,7 @@ name : w4d on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wca_push.yml b/.github/workflows/module_wca_push.yml index 798c576102..e4a77a1e67 100644 --- a/.github/workflows/module_wca_push.yml +++ b/.github/workflows/module_wca_push.yml @@ -2,7 +2,7 @@ name : wca on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_werror_push.yml b/.github/workflows/module_werror_push.yml index 09ad347aa2..563ab2e63f 100644 --- a/.github/workflows/module_werror_push.yml +++ b/.github/workflows/module_werror_push.yml @@ -2,7 +2,7 @@ name : werror on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_willbe_2_push.yml b/.github/workflows/module_willbe_2_push.yml index ae4e87a73c..65631a35b5 100644 --- a/.github/workflows/module_willbe_2_push.yml +++ b/.github/workflows/module_willbe_2_push.yml @@ -2,7 +2,7 @@ name : willbe2 on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_willbe_old_push.yml b/.github/workflows/module_willbe_old_push.yml index 6a8e058144..231a5784dd 100644 --- a/.github/workflows/module_willbe_old_push.yml +++ b/.github/workflows/module_willbe_old_push.yml @@ -2,7 +2,7 @@ name : willbe_old on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_willbe_push.yml b/.github/workflows/module_willbe_push.yml index 4cf9a39d0b..701f8cdc2f 100644 --- a/.github/workflows/module_willbe_push.yml +++ b/.github/workflows/module_willbe_push.yml @@ -2,7 +2,7 @@ name : willbe on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_winterval_push.yml b/.github/workflows/module_winterval_push.yml index 7262cf18d6..39c87febbf 100644 --- a/.github/workflows/module_winterval_push.yml +++ b/.github/workflows/module_winterval_push.yml @@ -2,7 +2,7 @@ name : winterval on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wlang_push.yml b/.github/workflows/module_wlang_push.yml index 02552efea4..ab1bbd2c96 100644 --- a/.github/workflows/module_wlang_push.yml +++ b/.github/workflows/module_wlang_push.yml @@ -2,7 +2,7 @@ name : wlang on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wplot_push.yml b/.github/workflows/module_wplot_push.yml index 647c12b523..833466e0d0 100644 --- a/.github/workflows/module_wplot_push.yml +++ b/.github/workflows/module_wplot_push.yml @@ -2,7 +2,7 @@ name : wplot on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wproc_macro_push.yml b/.github/workflows/module_wproc_macro_push.yml index a061484b02..c56d38c188 100644 --- a/.github/workflows/module_wproc_macro_push.yml +++ b/.github/workflows/module_wproc_macro_push.yml @@ -2,7 +2,7 @@ name : wproc_macro on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wstring_tools_push.yml b/.github/workflows/module_wstring_tools_push.yml index b2e3eae290..fdc4364c33 100644 --- a/.github/workflows/module_wstring_tools_push.yml +++ b/.github/workflows/module_wstring_tools_push.yml @@ -2,7 +2,7 @@ name : wstring_tools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wtest_basic_push.yml b/.github/workflows/module_wtest_basic_push.yml index f9e978ef45..420736676c 100644 --- a/.github/workflows/module_wtest_basic_push.yml +++ b/.github/workflows/module_wtest_basic_push.yml @@ -2,7 +2,7 @@ name : wtest_basic on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wtest_push.yml b/.github/workflows/module_wtest_push.yml index 3b4c5618c3..7cc9b8979f 100644 --- a/.github/workflows/module_wtest_push.yml +++ b/.github/workflows/module_wtest_push.yml @@ -2,7 +2,7 @@ name : wtest on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/module_wtools_push.yml b/.github/workflows/module_wtools_push.yml index 2b0ccb4642..fbbf8c7a3f 100644 --- a/.github/workflows/module_wtools_push.yml +++ b/.github/workflows/module_wtools_push.yml @@ -2,7 +2,7 @@ name : wtools on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/.github/workflows/standard_rust_push.yml b/.github/workflows/standard_rust_push.yml index 138307ad81..5e95425a98 100644 --- a/.github/workflows/standard_rust_push.yml +++ b/.github/workflows/standard_rust_push.yml @@ -39,7 +39,7 @@ jobs : runs-on: ubuntu-latest steps: - name: Install latest nightly toolchain - uses: Wandalen/wretry.action@master + uses: Wandalen/wretry.action/main@master with: action: actions-rs/toolchain@v1 with: | @@ -131,7 +131,7 @@ jobs : runs-on : ${{ matrix.os }} steps : - name : Install latest stable toolchain - uses : Wandalen/wretry.action@master + uses : Wandalen/wretry.action/main@master with : action : actions-rs/toolchain@v1 with : | @@ -140,7 +140,7 @@ jobs : attempt_limit : 3 attempt_delay: 10000 - name: Install latest nightly toolchain - uses: Wandalen/wretry.action@master + uses: Wandalen/wretry.action/main@master with: action: actions-rs/toolchain@v1 with: | diff --git a/.github/workflows/standard_rust_scheduled.yml b/.github/workflows/standard_rust_scheduled.yml index e39dbe1535..ac680e60bd 100644 --- a/.github/workflows/standard_rust_scheduled.yml +++ b/.github/workflows/standard_rust_scheduled.yml @@ -13,8 +13,6 @@ env : jobs : tested : - needs: check - if : ${{ needs.check.outputs.should_run == 'true' }} uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha with : manifest_path : './Cargo.toml' diff --git a/Cargo.toml b/Cargo.toml index b1594b7e6a..132c5b038f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ path = "module/alias/std_x" ## data_type [workspace.dependencies.data_type] -version = "~0.6.0" +version = "~0.7.0" path = "module/core/data_type" default-features = false @@ -92,7 +92,7 @@ default-features = false # path = "module/core/type_constructor_derive_pair_meta" [workspace.dependencies.interval_adapter] -version = "~0.18.0" +version = "~0.19.0" path = "module/core/interval_adapter" default-features = false features = [ "enabled" ] @@ -112,13 +112,13 @@ default-features = false ## derive [workspace.dependencies.derive_tools] -version = "~0.20.0" +version = "~0.21.0" path = "module/core/derive_tools" default-features = false features = [ "enabled" ] [workspace.dependencies.derive_tools_meta] -version = "~0.17.0" +version = "~0.18.0" path = "module/core/derive_tools_meta" default-features = false features = [ "enabled" ] @@ -146,19 +146,19 @@ path = "module/alias/fundamental_data_type" default-features = false [workspace.dependencies.variadic_from] -version = "~0.13.0" +version = "~0.14.0" path = "module/core/variadic_from" default-features = false features = [ "enabled" ] [workspace.dependencies.clone_dyn] -version = "~0.15.0" +version = "~0.16.0" path = "module/core/clone_dyn" default-features = false features = [ "enabled" ] [workspace.dependencies.clone_dyn_meta] -version = "~0.15.0" +version = "~0.16.0" path = "module/core/clone_dyn_meta" features = [ "enabled" ] @@ -166,7 +166,7 @@ features = [ "enabled" ] ## mem [workspace.dependencies.mem_tools] -version = "~0.5.0" +version = "~0.6.0" path = "module/core/mem_tools" default-features = false @@ -174,7 +174,7 @@ default-features = false ## diagnostics [workspace.dependencies.diagnostics_tools] -version = "~0.7.0" +version = "~0.8.0" path = "module/core/diagnostics_tools" default-features = false @@ -182,7 +182,7 @@ default-features = false ## iter [workspace.dependencies.iter_tools] -version = "~0.15.0" +version = "~0.16.0" path = "module/core/iter_tools" default-features = false @@ -190,41 +190,46 @@ default-features = false ## meta [workspace.dependencies.meta_tools] -version = "~0.9.0" +version = "~0.10.0" path = "module/core/meta_tools" default-features = false [workspace.dependencies.for_each] -version = "~0.7.0" +version = "~0.8.0" path = "module/core/for_each" default-features = false [workspace.dependencies.former] -version = "~0.16.0" +version = "~1.0.0" path = "module/core/former" default-features = false +[workspace.dependencies.former_stable] +package = "former" +version = "=0.15.0" +default-features = false + [workspace.dependencies.former_meta] -version = "~0.14.0" +version = "~1.0.0" path = "module/core/former_meta" default-features = false [workspace.dependencies.impls_index] -version = "~0.6.0" +version = "~0.7.0" path = "module/core/impls_index" default-features = false [workspace.dependencies.impls_index_meta] -version = "~0.6.0" +version = "~0.7.0" path = "module/core/impls_index_meta" [workspace.dependencies.mod_interface] -version = "~0.17.0" +version = "~0.18.0" path = "module/core/mod_interface" default-features = false [workspace.dependencies.mod_interface_meta] -version = "~0.17.0" +version = "~0.18.0" path = "module/core/mod_interface_meta" default-features = false @@ -250,7 +255,7 @@ default-features = false ## macro tools [workspace.dependencies.macro_tools] -version = "~0.23.0" +version = "~0.24.0" path = "module/core/macro_tools" default-features = false @@ -276,12 +281,12 @@ default-features = false ## typing [workspace.dependencies.typing_tools] -version = "~0.7.0" +version = "~0.8.0" path = "module/core/typing_tools" default-features = false [workspace.dependencies.implements] -version = "~0.7.0" +version = "~0.8.0" path = "module/core/implements" default-features = false @@ -291,12 +296,12 @@ path = "module/alias/instance_of" default-features = false [workspace.dependencies.inspect_type] -version = "~0.9.0" +version = "~0.10.0" path = "module/core/inspect_type" default-features = false [workspace.dependencies.is_slice] -version = "~0.8.0" +version = "~0.9.0" path = "module/core/is_slice" default-features = false @@ -304,7 +309,7 @@ default-features = false ## error [workspace.dependencies.error_tools] -version = "~0.12.0" +version = "~0.13.0" path = "module/core/error_tools" default-features = false @@ -324,6 +329,7 @@ default-features = false version = "~0.2.0" path = "module/alias/wstring_tools" + ## fs tools / path tools [workspace.dependencies.fs_tools] @@ -349,6 +355,11 @@ version = "~0.3.0" path = "module/core/process_tools" default-features = false +[workspace.dependencies.process_tools_published] +package = "process_tools" +version = "~0.2.0" +default-features = false + ## test @@ -357,7 +368,7 @@ version = "~0.4.0" path = "module/alias/wtest" [workspace.dependencies.test_tools] -version = "~0.8.0" +version = "~0.9.0" path = "module/core/test_tools" [workspace.dependencies.wtest_basic] @@ -437,7 +448,7 @@ version = "~0.2.0" path = "module/move/sqlx_query" [workspace.dependencies.deterministic_rand] -version = "~0.4.0" +version = "~0.5.0" path = "module/move/deterministic_rand" [workspace.dependencies.crates_tools] @@ -472,8 +483,3 @@ default-features = true version = "~0.3.0" path = "module/test/c" default-features = true - - -# [patch.crates-io] -# pathfinder_geometry = { git = "https://github.com/servo/pathfinder.git" } -# pathfinder_simd = { git = "https://github.com/servo/pathfinder.git" } diff --git a/Readme.md b/Readme.md index 7a48c4c316..68922b6d86 100644 --- a/Readme.md +++ b/Readme.md @@ -4,7 +4,7 @@ -[![alpha](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/StandardRustScheduled.yml?branch=master&label=alpha&logo=github)](https://github.com/Wandalen/wTools/actions/workflows/StandardRustStatus.yml) +[![alpha](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/auto_merge_to_beta.yml?label=alpha&logo=github&branch=alpha)](https://github.com/Wandalen/wTools/blob/master/.github/workflows/auto_merge_to_beta.yml) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwtools_trivial_sample%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wtools_trivial_sample/https://github.com/Wandalen/wTools) [![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/wtools) @@ -18,41 +18,41 @@ Collection of general purpose tools for solving problems. Fundamentally extend t | Module | Stability | master | alpha | Docs | Sample | |--------|-----------|--------|--------|:----:|:------:| -| [diagnostics_tools](module/core/diagnostics_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_diagnostics_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_diagnostics_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/diagnostics_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fdiagnostics_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20diagnostics_tools_trivial/https://github.com/Wandalen/wTools) | -| [mem_tools](module/core/mem_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mem_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mem_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mem_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmem_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20mem_tools_trivial/https://github.com/Wandalen/wTools) | -| [iter_tools](module/core/iter_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_iter_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_iter_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/iter_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fiter_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20iter_tools_trivial/https://github.com/Wandalen/wTools) | -| [interval_adapter](module/core/interval_adapter) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_interval_adapter_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_interval_adapter_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/interval_adapter) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Finterval_adapter_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20interval_adapter_trivial/https://github.com/Wandalen/wTools) | -| [macro_tools](module/core/macro_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_macro_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_macro_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/macro_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmacro_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20macro_tools_trivial/https://github.com/Wandalen/wTools) | -| [derive_tools_meta](module/core/derive_tools_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/derive_tools_meta) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fderive_tools_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20derive_tools_meta_trivial/https://github.com/Wandalen/wTools) | -| [variadic_from](module/core/variadic_from) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_variadic_from_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_variadic_from_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/variadic_from) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fvariadic_from_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20variadic_from_trivial/https://github.com/Wandalen/wTools) | -| [clone_dyn_meta](module/core/clone_dyn_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/clone_dyn_meta) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fclone_dyn_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20clone_dyn_meta_trivial/https://github.com/Wandalen/wTools) | -| [clone_dyn](module/core/clone_dyn) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/clone_dyn) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fclone_dyn_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20clone_dyn_trivial/https://github.com/Wandalen/wTools) | -| [derive_tools](module/core/derive_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/derive_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fderive_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20derive_tools_trivial/https://github.com/Wandalen/wTools) | -| [impls_index_meta](module/core/impls_index_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/impls_index_meta) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fimpls_index_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20impls_index_meta_trivial/https://github.com/Wandalen/wTools) | -| [impls_index](module/core/impls_index) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/impls_index) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fimpls_index_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20impls_index_trivial/https://github.com/Wandalen/wTools) | -| [error_tools](module/core/error_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_error_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_error_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/error_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ferror_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20error_tools_trivial/https://github.com/Wandalen/wTools) | -| [time_tools](module/core/time_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_time_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_time_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/time_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftime_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20time_tools_trivial/https://github.com/Wandalen/wTools) | -| [is_slice](module/core/is_slice) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_is_slice_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_is_slice_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/is_slice) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fis_slice_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20is_slice_trivial/https://github.com/Wandalen/wTools) | -| [implements](module/core/implements) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_implements_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_implements_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/implements) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fimplements_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20implements_trivial/https://github.com/Wandalen/wTools) | -| [inspect_type](module/core/inspect_type) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_inspect_type_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_inspect_type_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/inspect_type) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Finspect_type_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20inspect_type_trivial/https://github.com/Wandalen/wTools) | -| [typing_tools](module/core/typing_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_typing_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_typing_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/typing_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftyping_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20typing_tools_trivial/https://github.com/Wandalen/wTools) | -| [data_type](module/core/data_type) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_data_type_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_data_type_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/data_type) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fdata_type_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20data_type_trivial/https://github.com/Wandalen/wTools) | -| [former_meta](module/core/former_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/former_meta) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fformer_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20former_meta_trivial/https://github.com/Wandalen/wTools) | -| [collection_tools](module/core/collection_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_collection_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_collection_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/collection_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fcollection_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20collection_tools_trivial/https://github.com/Wandalen/wTools) | -| [former](module/core/former) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/former) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fformer_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20former_trivial/https://github.com/Wandalen/wTools) | -| [strs_tools](module/core/strs_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_strs_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_strs_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/strs_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fstrs_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20strs_tools_trivial/https://github.com/Wandalen/wTools) | -| [mod_interface_meta](module/core/mod_interface_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mod_interface_meta) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmod_interface_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20mod_interface_meta_trivial/https://github.com/Wandalen/wTools) | -| [mod_interface](module/core/mod_interface) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mod_interface) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmod_interface_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20mod_interface_trivial/https://github.com/Wandalen/wTools) | -| [for_each](module/core/for_each) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_for_each_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_for_each_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/for_each) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ffor_each_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20for_each_trivial/https://github.com/Wandalen/wTools) | -| [meta_tools](module/core/meta_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_meta_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_meta_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/meta_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmeta_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20meta_tools_trivial/https://github.com/Wandalen/wTools) | -| [wtools](module/core/wtools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wtools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wtools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/wtools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwtools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wtools_trivial/https://github.com/Wandalen/wTools) | -| [proper_path_tools](module/core/proper_path_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_proper_path_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_proper_path_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/proper_path_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fproper_path_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20proper_path_tools_trivial/https://github.com/Wandalen/wTools) | -| [reflect_tools_meta](module/core/reflect_tools_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/reflect_tools_meta) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Freflect_tools_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20reflect_tools_meta_trivial/https://github.com/Wandalen/wTools) | -| [fs_tools](module/core/fs_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_fs_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_fs_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/fs_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ffs_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20fs_tools_trivial/https://github.com/Wandalen/wTools) | -| [include_md](module/core/include_md) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_include_md_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_include_md_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/include_md) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Finclude_md_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20include_md_trivial/https://github.com/Wandalen/wTools) | -| [reflect_tools](module/core/reflect_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/reflect_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Freflect_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20reflect_tools_trivial/https://github.com/Wandalen/wTools) | -| [process_tools](module/core/process_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_process_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_process_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/process_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fprocess_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20process_tools_trivial/https://github.com/Wandalen/wTools) | -| [test_tools](module/core/test_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_test_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_test_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/test_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftest_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20test_tools_trivial/https://github.com/Wandalen/wTools) | +| [interval_adapter](module/core/interval_adapter) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_interval_adapter_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_interval_adapter_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/interval_adapter) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Finterval_adapter%2Fexamples%2Finterval_adapter_trivial.rs,RUN_POSTFIX=--example%20interval_adapter_trivial/https://github.com/Wandalen/wTools) | +| [iter_tools](module/core/iter_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_iter_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_iter_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/iter_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fiter_tools%2Fexamples%2Fiter_tools_trivial.rs,RUN_POSTFIX=--example%20iter_tools_trivial/https://github.com/Wandalen/wTools) | +| [macro_tools](module/core/macro_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_macro_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_macro_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/macro_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmacro_tools%2Fexamples%2Fmacro_tools_trivial.rs,RUN_POSTFIX=--example%20macro_tools_trivial/https://github.com/Wandalen/wTools) | +| [clone_dyn_meta](module/core/clone_dyn_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/clone_dyn_meta) | | +| [derive_tools_meta](module/core/derive_tools_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/derive_tools_meta) | | +| [clone_dyn](module/core/clone_dyn) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_clone_dyn_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/clone_dyn) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fclone_dyn%2Fexamples%2Fclone_dyn_trivial.rs,RUN_POSTFIX=--example%20clone_dyn_trivial/https://github.com/Wandalen/wTools) | +| [variadic_from](module/core/variadic_from) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_variadic_from_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_variadic_from_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/variadic_from) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fvariadic_from%2Fexamples%2Fvariadic_from_trivial.rs,RUN_POSTFIX=--example%20variadic_from_trivial/https://github.com/Wandalen/wTools) | +| [derive_tools](module/core/derive_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_derive_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/derive_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fderive_tools%2Fexamples%2Fderive_tools_trivial.rs,RUN_POSTFIX=--example%20derive_tools_trivial/https://github.com/Wandalen/wTools) | +| [mod_interface_meta](module/core/mod_interface_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mod_interface_meta) | | +| [collection_tools](module/core/collection_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_collection_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_collection_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/collection_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fcollection_tools%2Fexamples%2Fcollection_tools_trivial.rs,RUN_POSTFIX=--example%20collection_tools_trivial/https://github.com/Wandalen/wTools) | +| [former_meta](module/core/former_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/former_meta) | | +| [impls_index_meta](module/core/impls_index_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/impls_index_meta) | | +| [mod_interface](module/core/mod_interface) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mod_interface_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mod_interface) | | +| [error_tools](module/core/error_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_error_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_error_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/error_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ferror_tools%2Fexamples%2Ferror_tools_trivial.rs,RUN_POSTFIX=--example%20error_tools_trivial/https://github.com/Wandalen/wTools) | +| [for_each](module/core/for_each) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_for_each_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_for_each_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/for_each) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ffor_each%2Fexamples%2Ffor_each_map_style_sample.rs,RUN_POSTFIX=--example%20for_each_map_style_sample/https://github.com/Wandalen/wTools) | +| [former](module/core/former) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_former_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/former) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fformer%2Fexamples%2Fformer_trivial.rs,RUN_POSTFIX=--example%20former_trivial/https://github.com/Wandalen/wTools) | +| [implements](module/core/implements) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_implements_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_implements_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/implements) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fimplements%2Fexamples%2Fimplements_trivial_sample.rs,RUN_POSTFIX=--example%20implements_trivial_sample/https://github.com/Wandalen/wTools) | +| [impls_index](module/core/impls_index) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_impls_index_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/impls_index) | | +| [inspect_type](module/core/inspect_type) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_inspect_type_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_inspect_type_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/inspect_type) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Finspect_type%2Fexamples%2Finspect_type_trivial.rs,RUN_POSTFIX=--example%20inspect_type_trivial/https://github.com/Wandalen/wTools) | +| [is_slice](module/core/is_slice) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_is_slice_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_is_slice_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/is_slice) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fis_slice%2Fexamples%2Fis_slice_trivial_sample.rs,RUN_POSTFIX=--example%20is_slice_trivial_sample/https://github.com/Wandalen/wTools) | +| [proper_path_tools](module/core/proper_path_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_proper_path_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_proper_path_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/proper_path_tools) | | +| [data_type](module/core/data_type) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_data_type_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_data_type_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/data_type) | | +| [diagnostics_tools](module/core/diagnostics_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_diagnostics_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_diagnostics_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/diagnostics_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fdiagnostics_tools%2Fexamples%2Fdiagnostics_tools_trivial.rs,RUN_POSTFIX=--example%20diagnostics_tools_trivial/https://github.com/Wandalen/wTools) | +| [mem_tools](module/core/mem_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mem_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_mem_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/mem_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fmem_tools%2Fexamples%2Fmem_tools_trivial_sample.rs,RUN_POSTFIX=--example%20mem_tools_trivial_sample/https://github.com/Wandalen/wTools) | +| [meta_tools](module/core/meta_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_meta_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_meta_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/meta_tools) | | +| [process_tools](module/core/process_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_process_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_process_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/process_tools) | | +| [reflect_tools_meta](module/core/reflect_tools_meta) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_meta_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_meta_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/reflect_tools_meta) | | +| [strs_tools](module/core/strs_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_strs_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_strs_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/strs_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fstrs_tools%2Fexamples%2Fstr_toolst_trivial_sample.rs,RUN_POSTFIX=--example%20str_toolst_trivial_sample/https://github.com/Wandalen/wTools) | +| [time_tools](module/core/time_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_time_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_time_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/time_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftime_tools%2Fexamples%2Ftime_tools_trivial_sample.rs,RUN_POSTFIX=--example%20time_tools_trivial_sample/https://github.com/Wandalen/wTools) | +| [typing_tools](module/core/typing_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_typing_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_typing_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/typing_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Ftyping_tools%2Fexamples%2Ftyping_tools_trivial_sample.rs,RUN_POSTFIX=--example%20typing_tools_trivial_sample/https://github.com/Wandalen/wTools) | +| [fs_tools](module/core/fs_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_fs_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_fs_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/fs_tools) | | +| [include_md](module/core/include_md) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_include_md_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_include_md_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/include_md) | | +| [reflect_tools](module/core/reflect_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_reflect_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/reflect_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Freflect_tools%2Fexamples%2Freflect_tools_trivial.rs,RUN_POSTFIX=--example%20reflect_tools_trivial/https://github.com/Wandalen/wTools) | +| [test_tools](module/core/test_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_test_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_test_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/test_tools) | | +| [wtools](module/core/wtools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wtools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wtools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/wtools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fcore%2Fwtools%2Fexamples%2Fmain.rs,RUN_POSTFIX=--example%20main/https://github.com/Wandalen/wTools) | ### Rust modules to be moved out to other repositories @@ -60,17 +60,17 @@ Collection of general purpose tools for solving problems. Fundamentally extend t | Module | Stability | master | alpha | Docs | Sample | |--------|-----------|--------|--------|:----:|:------:| -| [wca](module/move/wca) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wca_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wca_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/wca) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwca_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wca_trivial/https://github.com/Wandalen/wTools) | -| [deterministic_rand](module/move/deterministic_rand) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_deterministic_rand_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_deterministic_rand_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/deterministic_rand) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fdeterministic_rand_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20deterministic_rand_trivial/https://github.com/Wandalen/wTools) | -| [optimization_tools](module/move/optimization_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_optimization_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_optimization_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/optimization_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Foptimization_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20optimization_tools_trivial/https://github.com/Wandalen/wTools) | -| [wplot](module/move/wplot) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wplot_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wplot_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/wplot) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwplot_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wplot_trivial/https://github.com/Wandalen/wTools) | -| [plot_interface](module/move/plot_interface) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_plot_interface_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_plot_interface_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/plot_interface) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fplot_interface_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20plot_interface_trivial/https://github.com/Wandalen/wTools) | -| [sqlx_query](module/move/sqlx_query) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_sqlx_query_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_sqlx_query_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/sqlx_query) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fsqlx_query_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20sqlx_query_trivial/https://github.com/Wandalen/wTools) | -| [unitore](module/move/unitore) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_unitore_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_unitore_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/unitore) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Funitore_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20unitore_trivial/https://github.com/Wandalen/wTools) | -| [refiner](module/move/refiner) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_refiner_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_refiner_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/refiner) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Frefiner_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20refiner_trivial/https://github.com/Wandalen/wTools) | -| [crates_tools](module/move/crates_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_crates_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_crates_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/crates_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fcrates_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20crates_tools_trivial/https://github.com/Wandalen/wTools) | -| [willbe](module/move/willbe) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_willbe_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_willbe_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/willbe) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwillbe_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20willbe_trivial/https://github.com/Wandalen/wTools) | -| [graphs_tools](module/move/graphs_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_graphs_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_graphs_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/graphs_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fgraphs_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20graphs_tools_trivial/https://github.com/Wandalen/wTools) | +| [crates_tools](module/move/crates_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_crates_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_crates_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/crates_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fcrates_tools%2Fexamples%2Fshow_crate_content.rs,RUN_POSTFIX=--example%20show_crate_content/https://github.com/Wandalen/wTools) | +| [deterministic_rand](module/move/deterministic_rand) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_deterministic_rand_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_deterministic_rand_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/deterministic_rand) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fdeterministic_rand%2Fexamples%2Fsample_deterministic_rand_rayon.rs,RUN_POSTFIX=--example%20sample_deterministic_rand_rayon/https://github.com/Wandalen/wTools) | +| [wca](module/move/wca) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wca_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wca_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/wca) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fwca%2Fexamples%2Fwca_trivial.rs,RUN_POSTFIX=--example%20wca_trivial/https://github.com/Wandalen/wTools) | +| [wplot](module/move/wplot) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wplot_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_wplot_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/wplot) | | +| [graphs_tools](module/move/graphs_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_graphs_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_graphs_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/graphs_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Fgraphs_tools%2Fexamples%2Fgraphs_tools_trivial_sample.rs,RUN_POSTFIX=--example%20graphs_tools_trivial_sample/https://github.com/Wandalen/wTools) | +| [optimization_tools](module/move/optimization_tools) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_optimization_tools_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_optimization_tools_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/optimization_tools) | [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module%2Fmove%2Foptimization_tools%2Fexamples%2Fcustom_problem.rs,RUN_POSTFIX=--example%20custom_problem/https://github.com/Wandalen/wTools) | +| [plot_interface](module/move/plot_interface) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_plot_interface_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_plot_interface_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/plot_interface) | | +| [refiner](module/move/refiner) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_refiner_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_refiner_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/refiner) | | +| [sqlx_query](module/move/sqlx_query) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_sqlx_query_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_sqlx_query_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/sqlx_query) | | +| [unitore](module/move/unitore) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_unitore_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_unitore_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/unitore) | | +| [willbe](module/move/willbe) | [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_willbe_push.yml?label=&branch=master)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml?query=branch%3Amaster) | [![rust-status](https://img.shields.io/github/actions/workflow/status/Wandalen/wTools/module_willbe_push.yml?label=&branch=alpha)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml?query=branch%3Aalpha) | [![docs.rs](https://raster.shields.io/static/v1?label=&message=docs&color=eee)](https://docs.rs/willbe) | | Collection of general purpose tools for solving problems. Fundamentally extend the language without spoiling, so may be used solely or in conjunction with another module of such kind. diff --git a/module/alias/cargo_will/Readme.md b/module/alias/cargo_will/Readme.md index df0d0f30ee..10c35c5726 100644 --- a/module/alias/cargo_will/Readme.md +++ b/module/alias/cargo_will/Readme.md @@ -1,7 +1,6 @@ # Module :: cargo_will - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_cargo_will_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_cargo_will_push.yml)[![docs.rs](https://img.shields.io/docsrs/cargo_will?color=e3e8f0&logo=docs.rs)](https://docs.rs/cargo_will)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fcargo_will_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20cargo_will_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_cargo_will_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_cargo_will_push.yml) [![docs.rs](https://img.shields.io/docsrs/cargo_will?color=e3e8f0&logo=docs.rs)](https://docs.rs/cargo_will) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Utility to publish multi-crate and multi-workspace environments and maintain their consistency. diff --git a/module/alias/file_tools/Readme.md b/module/alias/file_tools/Readme.md index 8be3f42567..2d47fabbab 100644 --- a/module/alias/file_tools/Readme.md +++ b/module/alias/file_tools/Readme.md @@ -1,6 +1,5 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_file_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_file_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/file_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/file_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ffile_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20file_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_file_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_file_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/file_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/file_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) # Module :: file_tools diff --git a/module/alias/fundamental_data_type/Readme.md b/module/alias/fundamental_data_type/Readme.md index e5daf36d9e..6bc40755ec 100644 --- a/module/alias/fundamental_data_type/Readme.md +++ b/module/alias/fundamental_data_type/Readme.md @@ -2,8 +2,7 @@ # Module :: fundamental_data_type - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_fundamental_data_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_fundamental_data_type_push.yml)[![docs.rs](https://img.shields.io/docsrs/fundamental_data_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/fundamental_data_type)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ffundamental_data_type_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20fundamental_data_type_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_fundamental_data_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_fundamental_data_type_push.yml) [![docs.rs](https://img.shields.io/docsrs/fundamental_data_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/fundamental_data_type) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) A collection of derive macros designed to enhance STD. diff --git a/module/alias/instance_of/Readme.md b/module/alias/instance_of/Readme.md index 53b5dfda3a..b926167a84 100644 --- a/module/alias/instance_of/Readme.md +++ b/module/alias/instance_of/Readme.md @@ -2,8 +2,7 @@ # Module :: instance_of - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_instance_of_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_instance_of_push.yml)[![docs.rs](https://img.shields.io/docsrs/instance_of?color=e3e8f0&logo=docs.rs)](https://docs.rs/instance_of)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Finstance_of_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20instance_of_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_instance_of_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_instance_of_push.yml) [![docs.rs](https://img.shields.io/docsrs/instance_of?color=e3e8f0&logo=docs.rs)](https://docs.rs/instance_of) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Macro to answer the question: does it implement a trait? diff --git a/module/alias/multilayer/Readme.md b/module/alias/multilayer/Readme.md index ca9526ae90..2ce79f6c18 100644 --- a/module/alias/multilayer/Readme.md +++ b/module/alias/multilayer/Readme.md @@ -2,8 +2,7 @@ # Module :: multilayer - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_multilayer_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_multilayer_push.yml)[![docs.rs](https://img.shields.io/docsrs/multilayer?color=e3e8f0&logo=docs.rs)](https://docs.rs/multilayer)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmultilayer_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20multilayer_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_multilayer_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_multilayer_push.yml) [![docs.rs](https://img.shields.io/docsrs/multilayer?color=e3e8f0&logo=docs.rs)](https://docs.rs/multilayer) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Protocol of modularity unifying interface of a module and introducing layers. diff --git a/module/alias/proc_macro_tools/Readme.md b/module/alias/proc_macro_tools/Readme.md index 1f67bc17d6..bdf7031c4d 100644 --- a/module/alias/proc_macro_tools/Readme.md +++ b/module/alias/proc_macro_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: proc_macro_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/proc_macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proc_macro_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fproc_macro_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20proc_macro_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proc_macro_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/proc_macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proc_macro_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/alias/proc_macro_tools/examples/proc_macro_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/alias/proc_macro_tools/examples/proc_macro_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing procedural macros. diff --git a/module/alias/proper_tools/Readme.md b/module/alias/proper_tools/Readme.md index 9101308f80..745078fa5b 100644 --- a/module/alias/proper_tools/Readme.md +++ b/module/alias/proper_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: proper_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proper_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/proper_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proper_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fproper_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20proper_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proper_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/proper_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proper_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools for solving problems. Fundamentally extend the language without spoiling, so may be used solely or in conjunction with another module of such kind. diff --git a/module/alias/werror/Readme.md b/module/alias/werror/Readme.md index 2e3b56c79e..5c33e4a695 100644 --- a/module/alias/werror/Readme.md +++ b/module/alias/werror/Readme.md @@ -2,8 +2,7 @@ # Module :: werror - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml)[![docs.rs](https://img.shields.io/docsrs/werror?color=e3e8f0&logo=docs.rs)](https://docs.rs/werror)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwerror_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20werror_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_werror_push.yml) [![docs.rs](https://img.shields.io/docsrs/werror?color=e3e8f0&logo=docs.rs)](https://docs.rs/werror) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/alias/werror/examples/werror_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/alias/werror/examples/werror_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Basic exceptions handling mechanism. diff --git a/module/alias/willbe2/Readme.md b/module/alias/willbe2/Readme.md index ad078ccbe1..544acb7210 100644 --- a/module/alias/willbe2/Readme.md +++ b/module/alias/willbe2/Readme.md @@ -1,7 +1,6 @@ # Module :: willbe2 - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_2_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_2_push.yml)[![docs.rs](https://img.shields.io/docsrs/willbe2?color=e3e8f0&logo=docs.rs)](https://docs.rs/willbe2)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwillbe2_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20willbe2_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_2_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_2_push.yml) [![docs.rs](https://img.shields.io/docsrs/willbe2?color=e3e8f0&logo=docs.rs)](https://docs.rs/willbe2) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Utility to publish multi-crate and multi-workspace environments and maintain their consistency. diff --git a/module/alias/winterval/Readme.md b/module/alias/winterval/Readme.md index e31fdfa097..72f6142126 100644 --- a/module/alias/winterval/Readme.md +++ b/module/alias/winterval/Readme.md @@ -2,8 +2,7 @@ # Module :: winterval - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml)[![docs.rs](https://img.shields.io/docsrs/winterval?color=e3e8f0&logo=docs.rs)](https://docs.rs/winterval)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwinterval_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20winterval_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_winterval_push.yml) [![docs.rs](https://img.shields.io/docsrs/winterval?color=e3e8f0&logo=docs.rs)](https://docs.rs/winterval) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/alias/winterval/examples/winterval_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/alias/winterval/examples/winterval_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Integer interval adapter for both Range and RangeInclusive. diff --git a/module/alias/wproc_macro/Readme.md b/module/alias/wproc_macro/Readme.md index bca33a386a..f3eef99bf7 100644 --- a/module/alias/wproc_macro/Readme.md +++ b/module/alias/wproc_macro/Readme.md @@ -2,8 +2,7 @@ # Module :: wproc_macro - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wproc_macro_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wproc_macro_push.yml)[![docs.rs](https://img.shields.io/docsrs/wproc_macro?color=e3e8f0&logo=docs.rs)](https://docs.rs/wproc_macro)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwproc_macro_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wproc_macro_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wproc_macro_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wproc_macro_push.yml) [![docs.rs](https://img.shields.io/docsrs/wproc_macro?color=e3e8f0&logo=docs.rs)](https://docs.rs/wproc_macro) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing procedural macros. diff --git a/module/alias/wstring_tools/Readme.md b/module/alias/wstring_tools/Readme.md index c5813e1b00..b04fcff48a 100644 --- a/module/alias/wstring_tools/Readme.md +++ b/module/alias/wstring_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: wstring_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/wstring_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wstring_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwstring_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wstring_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wstring_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/wstring_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wstring_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/alias/wstring_tools/examples/wstring_toolst_trivial_sample.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/alias/wstring_tools/examples/wstring_toolst_trivial_sample/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools to manipulate strings. diff --git a/module/alias/wtest/Readme.md b/module/alias/wtest/Readme.md index 3479675381..7b58479b1b 100644 --- a/module/alias/wtest/Readme.md +++ b/module/alias/wtest/Readme.md @@ -2,8 +2,7 @@ # Module :: wtest - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml)[![docs.rs](https://img.shields.io/docsrs/wtest?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtest)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwtest_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wtest_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_push.yml) [![docs.rs](https://img.shields.io/docsrs/wtest?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtest) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/alias/wtest/examples/wtest_trivial_sample.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/alias/wtest/examples/wtest_trivial_sample/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing and running tests. diff --git a/module/alias/wtest_basic/Readme.md b/module/alias/wtest_basic/Readme.md index 9989fa1e2f..bcc5d8e56f 100644 --- a/module/alias/wtest_basic/Readme.md +++ b/module/alias/wtest_basic/Readme.md @@ -2,8 +2,7 @@ # Module :: wtest_basic - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_basic_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_basic_push.yml)[![docs.rs](https://img.shields.io/docsrs/wtest_basic?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtest_basic)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwtest_basic_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wtest_basic_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_basic_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtest_basic_push.yml) [![docs.rs](https://img.shields.io/docsrs/wtest_basic?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtest_basic) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing and running tests. The most basic things. diff --git a/module/blank/exe_tools/Readme.md b/module/blank/exe_tools/Readme.md index 16678c27bd..4125d2ee58 100644 --- a/module/blank/exe_tools/Readme.md +++ b/module/blank/exe_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: exe_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_exe_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_exe_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/exe_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/exe_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fexe_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20exe_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_exe_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_exe_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/exe_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/exe_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of algorithms and structures to handle execution properly. diff --git a/module/blank/image_tools/Readme.md b/module/blank/image_tools/Readme.md index abfc9cfdca..c670c5a0e2 100644 --- a/module/blank/image_tools/Readme.md +++ b/module/blank/image_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: image_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_image_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_image_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/image_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/image_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fimage_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20image_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_image_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_image_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/image_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/image_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collections of algorithms and structures to process images. diff --git a/module/blank/math_tools/Readme.md b/module/blank/math_tools/Readme.md index ab0dfd8ee0..3e314ebc9c 100644 --- a/module/blank/math_tools/Readme.md +++ b/module/blank/math_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: math_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_math_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_math_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/math_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/math_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmath_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20math_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_math_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_math_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/math_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/math_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) To be done. diff --git a/module/blank/rustql/Cargo.toml b/module/blank/rustql/Cargo.toml new file mode 100644 index 0000000000..8d24519fb1 --- /dev/null +++ b/module/blank/rustql/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rustql" +version = "0.1.0" +edition = "2021" +authors = [ + "Kostiantyn Wandalen ", +] +license = "MIT" +readme = "Readme.md" +documentation = "https://docs.rs/rustql" +repository = "https://github.com/Wandalen/wTools/tree/master/module/core/rustql" +homepage = "https://github.com/Wandalen/wTools/tree/master/module/core/rustql" +description = """ +Rust query language. +""" +categories = [ "algorithms", "development-tools" ] +keywords = [ "fundamental", "general-purpose" ] + +[lints] +workspace = true + +[package.metadata.docs.rs] +features = [ "full" ] +all-features = false + +[features] +default = [ "enabled" ] +full = [ "enabled" ] +enabled = [] + +[dependencies] + +[dev-dependencies] +test_tools = { workspace = true } diff --git a/module/blank/rustql/License b/module/blank/rustql/License new file mode 100644 index 0000000000..6d5ef8559f --- /dev/null +++ b/module/blank/rustql/License @@ -0,0 +1,22 @@ +Copyright Kostiantyn W and Out of the Box Systems (c) 2013-2024 + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/module/blank/rustql/Readme.md b/module/blank/rustql/Readme.md new file mode 100644 index 0000000000..162de54d71 --- /dev/null +++ b/module/blank/rustql/Readme.md @@ -0,0 +1,33 @@ + + +# Module :: rustql +[![experimental](https://raster.shields.io/static/v1?label=stability&message=experimental&color=orange&logoColor=eee)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/ModulerustqlPush.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/ModulerustqlPush.yml) [![docs.rs](https://img.shields.io/docsrs/rustql?color=e3e8f0&logo=docs.rs)](https://docs.rs/rustql) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + +Rust query language. + + diff --git a/module/blank/rustql/src/lib.rs b/module/blank/rustql/src/lib.rs new file mode 100644 index 0000000000..e8dceed08a --- /dev/null +++ b/module/blank/rustql/src/lib.rs @@ -0,0 +1,11 @@ +#![ cfg_attr( feature = "no_std", no_std ) ] +#![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] +#![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] +#![ doc( html_root_url = "https://docs.rs/rustql/latest/rustql/" ) ] +#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] + +/// Function description. +#[ cfg( feature = "enabled" ) ] +pub fn f1() +{ +} diff --git a/module/blank/rustql/tests/inc/basic_test.rs b/module/blank/rustql/tests/inc/basic_test.rs new file mode 100644 index 0000000000..60c9a81cfb --- /dev/null +++ b/module/blank/rustql/tests/inc/basic_test.rs @@ -0,0 +1,7 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn basic() +{ +} diff --git a/module/blank/rustql/tests/inc/mod.rs b/module/blank/rustql/tests/inc/mod.rs new file mode 100644 index 0000000000..dde9de6f94 --- /dev/null +++ b/module/blank/rustql/tests/inc/mod.rs @@ -0,0 +1,4 @@ +#[ allow( unused_imports ) ] +use super::*; + +mod basic_test; diff --git a/module/blank/rustql/tests/smoke_test.rs b/module/blank/rustql/tests/smoke_test.rs new file mode 100644 index 0000000000..7fd288e61d --- /dev/null +++ b/module/blank/rustql/tests/smoke_test.rs @@ -0,0 +1,14 @@ + +// #[ cfg( feature = "default" ) ] +#[ test ] +fn local_smoke_test() +{ + ::test_tools::smoke_test_for_local_run(); +} + +// #[ cfg( feature = "default" ) ] +#[ test ] +fn published_smoke_test() +{ + ::test_tools::smoke_test_for_published_run(); +} diff --git a/module/blank/rustql/tests/tests.rs b/module/blank/rustql/tests/tests.rs new file mode 100644 index 0000000000..5a21773934 --- /dev/null +++ b/module/blank/rustql/tests/tests.rs @@ -0,0 +1,10 @@ + +include!( "../../../../module/step/meta/src/module/terminal.rs" ); + +#[ allow( unused_imports ) ] +use rustql as the_module; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ cfg( feature = "enabled" ) ] +mod inc; diff --git a/module/blank/w4d/Readme.md b/module/blank/w4d/Readme.md index db04225e8c..09648e3f1c 100644 --- a/module/blank/w4d/Readme.md +++ b/module/blank/w4d/Readme.md @@ -2,8 +2,7 @@ # Module :: math_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_w_4_d_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_w_4_d_push.yml)[![docs.rs](https://img.shields.io/docsrs/w4d?color=e3e8f0&logo=docs.rs)](https://docs.rs/w4d)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fw4d_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20w4d_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_w_4_d_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_w_4_d_push.yml) [![docs.rs](https://img.shields.io/docsrs/w4d?color=e3e8f0&logo=docs.rs)](https://docs.rs/w4d) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) To be done. diff --git a/module/blank/willbe_old/Readme.md b/module/blank/willbe_old/Readme.md index c84ec668b0..7028ba383c 100644 --- a/module/blank/willbe_old/Readme.md +++ b/module/blank/willbe_old/Readme.md @@ -2,8 +2,7 @@ # Module :: willbe - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_old_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_old_push.yml)[![docs.rs](https://img.shields.io/docsrs/willbe_old?color=e3e8f0&logo=docs.rs)](https://docs.rs/willbe_old)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwillbe_old_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20willbe_old_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_old_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_old_push.yml) [![docs.rs](https://img.shields.io/docsrs/willbe_old?color=e3e8f0&logo=docs.rs)](https://docs.rs/willbe_old) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) ___ diff --git a/module/blank/wlang/Readme.md b/module/blank/wlang/Readme.md index 11ad9cfb9c..cccb1180b6 100644 --- a/module/blank/wlang/Readme.md +++ b/module/blank/wlang/Readme.md @@ -2,8 +2,7 @@ # Module :: wlang - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wlang_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wlang_push.yml)[![docs.rs](https://img.shields.io/docsrs/wlang?color=e3e8f0&logo=docs.rs)](https://docs.rs/wlang)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwlang_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wlang_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wlang_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wlang_push.yml) [![docs.rs](https://img.shields.io/docsrs/wlang?color=e3e8f0&logo=docs.rs)](https://docs.rs/wlang) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Wlang. diff --git a/module/core/clone_dyn/Cargo.toml b/module/core/clone_dyn/Cargo.toml index dedc84a1d5..a409934586 100644 --- a/module/core/clone_dyn/Cargo.toml +++ b/module/core/clone_dyn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clone_dyn" -version = "0.15.0" +version = "0.16.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/clone_dyn/Readme.md b/module/core/clone_dyn/Readme.md index 836736761f..8187c3dc21 100644 --- a/module/core/clone_dyn/Readme.md +++ b/module/core/clone_dyn/Readme.md @@ -1,8 +1,7 @@ # Module :: clone_dyn - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml)[![docs.rs](https://img.shields.io/docsrs/clone_dyn?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fclone_dyn_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20clone_dyn_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_push.yml) [![docs.rs](https://img.shields.io/docsrs/clone_dyn?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/clone_dyn/examples/clone_dyn_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/clone_dyn/examples/clone_dyn_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Derive to clone dyn structures. diff --git a/module/core/clone_dyn/src/lib.rs b/module/core/clone_dyn/src/lib.rs index c75a03b46a..54e6f3ef40 100644 --- a/module/core/clone_dyn/src/lib.rs +++ b/module/core/clone_dyn/src/lib.rs @@ -4,6 +4,7 @@ #![ doc( html_root_url = "https://docs.rs/clone_dyn/latest/clone_dyn/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +#[ allow( unused_extern_crates ) ] #[ cfg( all( feature = "no_std", feature = "use_alloc" ) ) ] extern crate alloc; diff --git a/module/core/clone_dyn/tests/inc/mod.rs b/module/core/clone_dyn/tests/inc/mod.rs index 6477b35c67..99cee0a3c6 100644 --- a/module/core/clone_dyn/tests/inc/mod.rs +++ b/module/core/clone_dyn/tests/inc/mod.rs @@ -119,7 +119,7 @@ tests_impls! #[ clone_dyn ] trait Trait2< T1 : Copy, T2 : Copy > where - T2 : Clone + std::fmt::Debug, + T2 : Clone + core::fmt::Debug, { } diff --git a/module/core/clone_dyn_meta/Cargo.toml b/module/core/clone_dyn_meta/Cargo.toml index 07177a0234..0cc0f336f6 100644 --- a/module/core/clone_dyn_meta/Cargo.toml +++ b/module/core/clone_dyn_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clone_dyn_meta" -version = "0.15.0" +version = "0.16.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/clone_dyn_meta/Readme.md b/module/core/clone_dyn_meta/Readme.md index 6edde1ae08..bb46445c85 100644 --- a/module/core/clone_dyn_meta/Readme.md +++ b/module/core/clone_dyn_meta/Readme.md @@ -1,8 +1,7 @@ # Module :: clone_dyn_meta - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml)[![docs.rs](https://img.shields.io/docsrs/clone_dyn_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn_meta)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fclone_dyn_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20clone_dyn_meta_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_clone_dyn_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/clone_dyn_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/clone_dyn_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Derive to clone dyn structures. diff --git a/module/core/clone_dyn_meta/src/derive.rs b/module/core/clone_dyn_meta/src/derive.rs index 883d7b9bf6..023e852b39 100644 --- a/module/core/clone_dyn_meta/src/derive.rs +++ b/module/core/clone_dyn_meta/src/derive.rs @@ -16,7 +16,7 @@ pub fn clone_dyn( _attr : proc_macro::TokenStream, item : proc_macro::TokenStrea let name_ident = &item_parsed.ident; // let generics = &item_parsed.generics; let generics_analyzed = item_parsed.generics_analyze(); - let generics_params = &generics_analyzed.generics.params; + let generic_params = &generics_analyzed.generics.params; let generics_where = &generics_analyzed.generics.where_clause; let generics_names = &generics_analyzed.names; @@ -25,7 +25,7 @@ pub fn clone_dyn( _attr : proc_macro::TokenStream, item : proc_macro::TokenStrea #item_parsed #[ allow( non_local_definitions ) ] - impl < 'c, #generics_params > Clone + impl < 'c, #generic_params > Clone for Box< dyn #name_ident< #( #generics_names ),* > + 'c > // where #generics_where @@ -35,7 +35,7 @@ pub fn clone_dyn( _attr : proc_macro::TokenStream, item : proc_macro::TokenStrea } #[ allow( non_local_definitions ) ] - impl < 'c, #generics_params > Clone + impl < 'c, #generic_params > Clone for Box< dyn #name_ident< #( #generics_names ),* > + Send + 'c > // where #generics_where @@ -45,7 +45,7 @@ pub fn clone_dyn( _attr : proc_macro::TokenStream, item : proc_macro::TokenStrea } #[ allow( non_local_definitions ) ] - impl < 'c, #generics_params > Clone + impl < 'c, #generic_params > Clone for Box< dyn #name_ident< #( #generics_names ),* > + Sync + 'c > // where #generics_where @@ -55,7 +55,7 @@ pub fn clone_dyn( _attr : proc_macro::TokenStream, item : proc_macro::TokenStrea } #[ allow( non_local_definitions ) ] - impl < 'c, #generics_params > Clone + impl < 'c, #generic_params > Clone for Box< dyn #name_ident< #( #generics_names ),* > + Send + Sync + 'c > // where #generics_where diff --git a/module/core/collection_tools/Cargo.toml b/module/core/collection_tools/Cargo.toml index 14edcdd31e..197d4e2ca2 100644 --- a/module/core/collection_tools/Cargo.toml +++ b/module/core/collection_tools/Cargo.toml @@ -41,6 +41,7 @@ use_alloc = [ default = [ "enabled", "collection_constructors", + "collection_into_constructors", "collection_std", ] full = [ @@ -51,15 +52,16 @@ full = [ enabled = [] # Collection constructors, like `hmap!{ "key" => "val" }` -collection_constructors = [ "literally" ] +collection_constructors = [] +# Collection constructors, using `into()` under the hood, like `into_hmap!( "key" => "val" )` +collection_into_constructors = [] # STD collection for no_std. collection_std = [] - +# qqq : is this feature used? seems not. if yes, what is it responsible for? discuss [dependencies] ## external -literally = { version = "~0.1.3", optional = true, default-features = false } hashbrown = { version = "~0.14.3", optional = true, default-features = false, features = [ "default" ] } [dev-dependencies] diff --git a/module/core/collection_tools/Readme.md b/module/core/collection_tools/Readme.md index 733dad2b14..07893f301d 100644 --- a/module/core/collection_tools/Readme.md +++ b/module/core/collection_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: collection_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/collection_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/collection_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fcollection_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20collection_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_collection_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/collection_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/collection_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/collection_tools/examples/collection_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/collection_tools/examples/collection_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools to manipulate collections( containers like Vec/HashMap/HashSet... ). @@ -14,20 +13,59 @@ This module encompasses a suite of meta-tools designed to enhance Rust's collect Consider the following example, which demonstrates the use of the `hmap!` macro to effortlessly create a `HashMap`: - ```rust -# #[ cfg( not( feature = "use_alloc" ) ) ] # #[ cfg( all( feature = "enabled", feature = "collection_constructors" ) ) ] # #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] # { - use collection_tools::*; let meta_map = hmap! { 3 => 13 }; -let mut std_map = std::collections::HashMap::new(); + +// it is identical to `hashbrown::HashMap` if `use_alloc` feature is on, otherwise `std::collections::HashMap` +let mut std_map = collection_tools::HashMap::new(); + std_map.insert( 3, 13 ); assert_eq!( meta_map, std_map ); +# } +``` + +Another example, this time, `into_bset!`, providing you a `BTreeSet`: + +```rust +# #[ cfg( all( feature = "enabled", feature = "collection_constructors" ) ) ] +# #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] +# { +use collection_tools::*; + +let meta_set = bset! { 3, 13 }; + +// this `BTreeSet` is just a reexport from `alloc`, +// so it can be used in the same places as `alloc/std::BTreeSet` +let mut std_set = collection_tools::BTreeSet::new(); + +std_set.insert( 13 ); +std_set.insert( 3 ); +assert_eq!( meta_set, std_set ); +# } +``` + +Another example with `list!`: + +```rust +# #[ cfg( all( feature = "enabled", feature = "collection_constructors" ) ) ] +# #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] +# { +use collection_tools::*; + +let meta_list : LinkedList< i32 > = list! { 3, 13 }; + +// this `LinkedList` is just a reexport from `alloc`, +// so it can be used in the same places as `alloc/std::LinkedList` +let mut meta_list = collection_tools::LinkedList::new(); +meta_list.push_front( 13 ); +meta_list.push_front( 3 ); +assert_eq!( meta_list, meta_list ); # } ``` @@ -37,42 +75,72 @@ When implementing a `no_std` environment with the `use_alloc` feature in your Ru You can do - + ```rust -# #[ cfg( not( feature = "use_alloc" ) ) ] # #[ cfg( all( feature = "enabled", feature = "collection_std" ) ) ] # #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] # { +use collection_tools::HashSet; -use collection_tools::Vec; - -let mut map : Vec< i32 > = Vec::new(); -map.push( 1 ); -assert_eq!( map.first().unwrap().clone(), 1 ); - +let mut vec : HashSet< i32 > = HashSet::new(); +vec.insert( 1 ); +assert_eq!( vec.contains( &1 ), true ); # } ``` Instead of
-The code above will be expanded to this +Click to see ```rust +# #[ cfg( all( feature = "enabled", feature = "collection_std" ) ) ] +# #[ cfg( any( feature = "use_alloc", not( feature = "no_std" ) ) ) ] +# { + #[ cfg( feature = "use_alloc" ) ] -extern crate alloc; -#[ cfg( feature = "use_alloc" ) ] -use alloc::vec::Vec; +use hashbrown::HashSet; // a `no_std` replacement for `HashSet` #[ cfg( not( feature = "no_std" ) ) ] -use std::vec::Vec; +use std::collections::HashSet; -let mut collection : Vec< i32 > = Vec::new(); -collection.push( 1 ); -assert_eq!( collection.first().unwrap().clone(), 1 ); +let mut vec : HashSet< i32 > = HashSet::new(); +vec.insert( 1 ); +assert_eq!( vec.contains( &1 ), true ); + +# } ```
+### Basic Use Case :: `no_std` `HashSet` / `HashMap` + +The crate has two classes of macros: strict macros (the one we covered), which require that all collection members are of the same type; and more "relaxed" macros, that use under the hood `Into` trait to cast to a certain type. They can be accessed by prepending `into_` to name of a macro (`into_vec`, `into_bmap`, etc). + +While strict macros require you to have all members of the same type, more relaxed macros often require you to specify the desired type. So there's no a clear winner. Choose the right one for each situation separately. + +For example: +```rust +# #[ cfg( all( feature = "enabled", feature = "collection_into_constructors" ) ) ] +# { +use std::borrow::Cow; +let vec : Vec< String > = collection_tools::into_vec!( "&str", "String".to_string(), Cow::from( "Cow" ) ); +# } +``` + +Each strict macro has its relaxed counterpart. + +### Collections being used + +So what's the deal with `collection_tools::`? + +Nothing really fancy. We just reuse collections from `alloc` (same as `std`). + +But not all collections are available in `alloc` crate. For now, the exceptions are `HashMap` and `HashSet`. This leads to the fact that we can't use them in `no_std` environment. How did we solve this? By using those collections from `hashbrown` crate whenever `no_std` feature is enabled. You can found more details on origin of a collection on its documentation page. + +### MORE Examples + +If you are feeling confused about the syntax you should use for a macro, you can visit its documentation. It is saturated with different examples, so hopefully you'll not be stuck. + ### To add to your project ```sh diff --git a/module/core/collection_tools/examples/collection_tools_trivial.rs b/module/core/collection_tools/examples/collection_tools_trivial.rs index 71d45dd97e..b817a50c84 100644 --- a/module/core/collection_tools/examples/collection_tools_trivial.rs +++ b/module/core/collection_tools/examples/collection_tools_trivial.rs @@ -21,21 +21,21 @@ #[ cfg( not( all ( - not( feature = "use_alloc" ), +// not( feature = "use_alloc" ) ) ], all( feature = "enabled", feature = "collection_constructors" ), any( not( feature = "no_std" ), feature = "use_alloc" ) )))] fn main(){} -// zzz : qqq : rid off `#[ cfg( not( feature = "use_alloc" ) ) ]` -#[ cfg( not( feature = "use_alloc" ) ) ] +// zzz : aaa : rid off `#[ cfg( not( feature = "use_alloc" ) ) ]` -- Rid of by not relying on std +// #[ cfg( not( feature = "use_alloc" ) ) ] #[ cfg( all( feature = "enabled", feature = "collection_constructors" ) ) ] #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] fn main() { use collection_tools::*; let map = hmap! { 3 => 13 }; - let mut expected = std::collections::HashMap::new(); + let mut expected = collection_tools::HashMap::new(); expected.insert( 3, 13 ); assert_eq!( map, expected ); } diff --git a/module/core/collection_tools/src/constructors.rs b/module/core/collection_tools/src/constructors.rs new file mode 100644 index 0000000000..1ea42a5e62 --- /dev/null +++ b/module/core/collection_tools/src/constructors.rs @@ -0,0 +1,582 @@ +/// Creates a `BTreeMap` from a list of key-value pairs. +/// +/// The `bmap` macro facilitates the convenient creation of a `BTreeMap` with initial elements. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of key-value pairs. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ BTreeMap, bmap }; +/// // BTreeMap of &str to i32 +/// let map1 = bmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// +/// // BTreeMap of &str to &str +/// let map2 = bmap!{ "name" => "value" }; +/// +/// // With trailing comma +/// let map3 = bmap!( 1 => "one", 2 => "two", 3 => "three", ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `BTreeMap`. +/// Each key and value can be of any type that implements the `Into< K >` and `Into< V >` traits, where `K` and `V` are the +/// types stored in the `BTreeMap` as keys and values, respectively. +/// +/// # Returns +/// +/// Returns a `BTreeMap` containing all the specified key-value pairs. The map's capacity is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices and integer values: +/// +/// ```rust +/// # use collection_tools::{ BTreeMap, bmap }; +/// let map = bmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// assert_eq!( map.get( "one" ), Some( &1 ) ); +/// assert_eq!( map.get( "two" ), Some( &2 ) ); +/// assert_eq!( map.get( "three" ), Some( &3 ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `BTreeMap` of integers to string slices from literals: +/// +/// ```rust +/// # use collection_tools::{ BTreeMap, bmap }; +/// let numbers = bmap!( 1 => "one", 2 => "two", 3 => "three" ); +/// assert_eq!( numbers.get( &1 ), Some( &"one" ) ); +/// assert_eq!( numbers.get( &2 ), Some( &"two" ) ); +/// assert_eq!( numbers.get( &3 ), Some( &"three" ) ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! bmap +{ + ( + $( $key : expr => $value : expr ),* $( , )? + ) + => + {{ + let mut _map = collection_tools::BTreeMap::new(); + $( + let _ = _map.insert( $key , $value ); + )* + _map + }}; +} + +/// Creates a `BTreeSet` from a list of elements. +/// +/// The `bset` macro allows for convenient creation of a `BTreeSet` with initial elements. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ BTreeSet, bset }; +/// // BTreeSet of &str +/// let set1 = bset!( "a", "b", "c" ); +/// +/// // With trailing comma +/// let set3 = bset!( 1, 2, 3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BTreeSet`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BTreeSet`. +/// +/// # Returns +/// +/// Returns a `BTreeSet` containing all the specified elements. The capacity of the set is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices: +/// +/// ```rust +/// # use collection_tools::{ BTreeSet, bset }; +/// let set = bset!( "one", "two", "three" ); +/// assert!( set.contains( "one" ) ); +/// assert!( set.contains( "two" ) ); +/// assert!( set.contains( "three" ) ); +/// assert_eq!( set.len(), 3 ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! bset +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let mut _set = collection_tools::BTreeSet::new(); + $( + _set.insert( $key ); + )* + _set + }}; +} + +/// Creates a `BinaryHeap` from a list of elements. +/// +/// The `into_heap` macro simplifies the creation of a `BinaryHeap` with initial elements. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ BinaryHeap, heap }; +/// // BinaryHeap of i32 +/// let heap1 = heap!( 3, 1, 4, 1, 5, 9 ); +/// +/// // BinaryHeap of &str +/// let heap2 = heap!{ "pear", "apple", "banana" }; +/// +/// // With trailing comma +/// let heap3 = heap!( 2, 7, 1, 8, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BinaryHeap`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BinaryHeap`. +/// +/// # Returns +/// +/// Returns a `BinaryHeap` containing all the specified elements. The capacity of the heap is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{ BinaryHeap, heap }; +/// let heap = heap!( 5, 3, 7, 1 ); +/// assert_eq!( heap.peek(), Some( &7 ) ); // The largest value is at the top of the heap +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! heap +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _heap = collection_tools::BinaryHeap::with_capacity( _cap ); + $( + _heap.push( $key ); + )* + _heap + }}; +} + +/// Creates a `HashMap` from a list of key-value pairs. +/// +/// The `hmap` macro allows for convenient creation of a `HashMap` with initial elements. +/// +/// # Origin +/// +/// This collection can be reexported from different crates: +/// - from `std`, if `no_std` flag if off +/// - from `hashbrown`, if `use_alloc` flag if on +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of key-value pairs. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ HashMap, hmap }; +/// // HashMap of &str to i32 +/// let map1 = hmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// +/// // HashMap of &str to &str +/// let map2 = hmap!{ "name" => "value", "type" => "example" }; +/// +/// // With trailing comma +/// let map3 = hmap!( 1 => "one", 2 => "two", 3 => "three", ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `HashMap`. +/// Each key and value can be of any type that implements the `Into` and `Into` traits, where `K` and `V` are the +/// types stored in the `HashMap` as keys and values, respectively. +/// +/// # Returns +/// +/// Returns a `HashMap` containing all the specified key-value pairs. The capacity of the map is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices and integer values: +/// +/// ```rust +/// # use collection_tools::{ HashMap, hmap }; +/// let map : HashMap< &str, i32 > = hmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// assert_eq!( map.get( "one" ), Some( &1 ) ); +/// assert_eq!( map.get( "two" ), Some( &2 ) ); +/// assert_eq!( map.get( "three" ), Some( &3 ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `HashMap` of integers to strings from literals: +/// +/// ```rust +/// # use collection_tools::{ HashMap, hmap }; +/// let pairs = hmap!( 1 => "apple", 2 => "banana" ); +/// assert_eq!( pairs.get( &1 ), Some( &"apple" ) ); +/// assert_eq!( pairs.get( &2 ), Some( &"banana" ) ); +/// ``` +/// +#[macro_export(local_inner_macros)] +macro_rules! hmap +{ + ( + $( $key : expr => $value : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _map = collection_tools::HashMap::with_capacity( _cap ); + $( + let _ = _map.insert( $key, $value ); + )* + _map + }}; +} + +/// Creates a `HashSet` from a list of elements. +/// +/// The `hset` macro allows for convenient creation of a `HashSet` with initial elements. +/// +/// # Origin +/// +/// This collection can be reexported from different crates: +/// - from `std`, if `no_std` flag if off +/// - from `hashbrown`, if `use_alloc` flag if on +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ HashSet, hset }; +/// // HashSet of &str +/// let set1 = hset!( "a", "b", "c" ); +/// +/// // HashSet of &str +/// let set2 = hset!{ "a", "b", "c" }; +/// +/// // With trailing comma +/// let set3 = hset!( 1, 2, 3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `HashSet`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `HashSet`. +/// +/// # Returns +/// +/// Returns a `HashSet` containing all the specified elements. The capacity of the set is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices: +/// +/// ```rust +/// # use collection_tools::{ HashSet, hset }; +/// let set = hset!( "one", "two", "three" ); +/// assert!( set.contains( "one" ) ); +/// assert!( set.contains( "two" ) ); +/// assert!( set.contains( "three" ) ); +/// assert_eq!( set.len(), 3 ); +/// ``` +/// +/// # Example +/// +/// Creating a `HashSet` of `&str` from string literals: +/// +/// ```rust +/// # use collection_tools::{ HashSet, hset }; +/// let s = hset!{ "value" }; +/// assert_eq!( s.get( "value" ), Some( &"value" ) ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! hset +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _set = collection_tools::HashSet::with_capacity( _cap ); + $( + let _ = _set.insert( $key ); + )* + _set + }}; +} + +/// Creates a `LinkedList` from a list of elements. +/// +/// The `list` macro facilitates the creation of a `LinkedList` with initial elements. +/// +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ LinkedList, list }; +/// // LinkedList of i32 +/// let lst1 = list!( 1, 2, 3, 4, 5 ); +/// +/// // LinkedList of &str +/// let lst2 = list!{ "hello", "world", "rust" }; +/// +/// // With trailing comma +/// let lst3 = list!( 1.1, 2.2, 3.3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `LinkedList`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `LinkedList`. +/// +/// # Returns +/// +/// Returns a `LinkedList` containing all the specified elements. The capacity of the list is +/// dynamically adjusted based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{ LinkedList, list }; +/// let lst = list!( 1, 2, 3 ); +/// assert_eq!( lst.front(), Some( &1 ) ); // The first element is 1 +/// assert_eq!( lst.back(), Some( &3 ) ); // The last element is 3 +/// ``` +/// +/// # Example +/// +/// Creating a `LinkedList` of `&str` from string literals: +/// +/// ```rust +/// # use collection_tools::{ LinkedList, list }; +/// let fruits = list!{ "apple", "banana", "cherry" }; +/// assert_eq!( fruits.front(), Some( &"apple" ) ); // The first element +/// assert_eq!( fruits.back(), Some( &"cherry" ) ); // The last element +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! list +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + // "The LinkedList allows pushing and popping elements at either end in constant time." + // So no `with_capacity` + let mut _lst = collection_tools::LinkedList::new(); + $( + _lst.push_back( $key ); + )* + _lst + }}; +} + +/// Creates a `Vec` from a list of elements. +/// +/// The `vec` macro simplifies the creation of a `Vec` with initial elements. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{Vec, vec}; +/// // Vec of i32 +/// let vec1 = vec!( 1, 2, 3, 4, 5 ); +/// +/// // Vec of &str +/// let vec2 = vec!{ "hello", "world", "rust" }; +/// +/// // With trailing comma +/// let vec3 = vec!( 1.1, 2.2, 3.3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key : expr ),* $( , )?`: A comma-separated list of elements to insert into the `Vec`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `Vec`. +/// +/// # Returns +/// +/// Returns a `Vec` containing all the specified elements. The capacity of the vector is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{Vec, vec}; +/// let vec = vec!( 1, 2, 3 ); +/// assert_eq!( vec[ 0 ], 1 ); +/// assert_eq!( vec[ 1 ], 2 ); +/// assert_eq!( vec[ 2 ], 3 ); +/// ``` +/// +/// # Example +/// +/// Creating a `Vec` of `&str` from string literals: +/// +/// ```rust +/// # use collection_tools::{Vec, vec}; +/// let mixed = vec!{ "value", "another value" }; +/// assert_eq!( mixed[ 0 ], "value" ); +/// assert_eq!( mixed[ 1 ], "another value" ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! vec +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _vec = collection_tools::Vec::with_capacity( _cap ); + $( + _vec.push( $key ); + )* + _vec + }}; +} + +/// Creates a `VecDeque` from a list of elements. +/// +/// The `vecd` macro allows for the convenient creation of a `VecDeque` with initial elements. +/// Elements passed to the macro are automatically converted into the deque's element type +/// using `.into()`, enabling the use of literals or values of different, but convertible types. +/// +/// Note: The `vecd` macro relies on the `.into()` method to convert each element into the target type +/// of the `VecDeque`. This means that the elements must be compatible with the `Into` trait for the +/// type `T` used in the `VecDeque`. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ VecDeque, vecd }; +/// // VecDeque of i32 +/// let vd1 = vecd!( 1, 2, 3, 4, 5 ); +/// +/// // VecDeque of String +/// let vd2 = vecd!{ "hello", "world", "rust" }; +/// +/// // With trailing comma +/// let vd3 = vecd!( 1.1, 2.2, 3.3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `VecDeque`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `VecDeque`. +/// +/// # Returns +/// +/// Returns a `VecDeque` containing all the specified elements. The capacity of the deque is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{ VecDeque, vecd }; +/// let vd : VecDeque< i32 > = vecd!( 1, 2, 3 ); +/// assert_eq!( vd.front(), Some( &1 ) ); // The first element is 1 +/// assert_eq!( vd.back(), Some( &3 ) ); // The last element is 3 +/// ``` +/// +/// # Example +/// +/// Creating a `VecDeque` of `&str` from string literals: +/// +/// ```rust +/// # use collection_tools::{ VecDeque, vecd }; +/// let fruits = vecd!{ "apple", "banana", "cherry" }; +/// assert_eq!( fruits.front(), Some( &"apple" ) ); // The first element +/// assert_eq!( fruits.back(), Some( &"cherry" ) ); // The last element +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! vecd +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _vecd = collection_tools::VecDeque::with_capacity( _cap ); + $( + _vecd.push_back( $key ); + )* + _vecd + }}; +} diff --git a/module/core/collection_tools/src/into_constructors.rs b/module/core/collection_tools/src/into_constructors.rs new file mode 100644 index 0000000000..59af857a21 --- /dev/null +++ b/module/core/collection_tools/src/into_constructors.rs @@ -0,0 +1,738 @@ +/// Creates a `BTreeMap` from a list of key-value pairs. +/// +/// The `into_bmap` macro facilitates the convenient creation of a `BTreeMap` with initial elements. +/// Keys and values passed to the macro are automatically converted into the map's key and value types +/// using `.into()`, enabling the use of literals or values of different, but convertible types. +/// +/// Note: The `into_bmap` macro relies on the `.into()` method to convert each key and value into the target types +/// of the `BTreeMap`. This means that the keys and values must be compatible with the `Into< K >` and `Into< V >` traits +/// for the key type `K` and value type `V` used in the `BTreeMap`. Also, this means that sometimes you must specify the type of collection's items. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of key-value pairs. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ BTreeMap, into_bmap }; +/// // BTreeMap of &str to i32 +/// let map1 : BTreeMap< &str, i32 > = into_bmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// +/// // BTreeMap of String to String +/// let map2 : BTreeMap< String, String > = into_bmap!{ "name" => "value" }; +/// +/// // With trailing comma +/// let map3 : BTreeMap< i32, &str > = into_bmap!( 1 => "one", 2 => "two", 3 => "three", ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `BTreeMap`. +/// Each key and value can be of any type that implements the `Into< K >` and `Into< V >` traits, where `K` and `V` are the +/// types stored in the `BTreeMap` as keys and values, respectively. +/// +/// # Returns +/// +/// Returns a `BTreeMap` containing all the specified key-value pairs. The map's capacity is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices and integer values: +/// +/// ```rust +/// # use collection_tools::{ BTreeMap, into_bmap }; +/// let map : BTreeMap< &str, i32 > = into_bmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// assert_eq!( map.get( "one" ), Some( &1 ) ); +/// assert_eq!( map.get( "two" ), Some( &2 ) ); +/// assert_eq!( map.get( "three" ), Some( &3 ) ); +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into< K >` and `Into< V >`: +/// +/// ```rust +/// # use collection_tools::{ BTreeMap, into_bmap }; +/// let months : BTreeMap< String, i32 > = into_bmap!( "January" => 1, "February" => 2, "March" => 3 ); +/// assert_eq!( months.get( &"January".to_string() ), Some( &1 ) ); +/// assert_eq!( months.get( &"February".to_string() ), Some( &2 ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `BTreeMap` of integers to strings from literals: +/// +/// ```rust +/// # use collection_tools::{ BTreeMap, into_bmap }; +/// let numbers : BTreeMap< i32, String > = into_bmap!( 1 => "one", 2 => "two", 3 => "three" ); +/// assert_eq!( numbers.get( &1 ), Some( &"one".to_string() ) ); +/// assert_eq!( numbers.get( &2 ), Some( &"two".to_string() ) ); +/// assert_eq!( numbers.get( &3 ), Some( &"three".to_string() ) ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! into_bmap +{ + ( + $( $key : expr => $value : expr ),* $( , )? + ) + => + {{ + let mut _map = collection_tools::BTreeMap::new(); + $( + let _ = _map.insert( Into::into( $key ), Into::into( $value ) ); + )* + _map + }}; +} + +/// Creates a `BTreeSet` from a list of elements. +/// +/// The `into_bset` macro allows for convenient creation of a `BTreeSet` with initial elements. +/// Elements passed to the macro are automatically converted into the set's element type +/// using `.into()`, facilitating the use of literals or values of different, but convertible types. +/// +/// Note: The `into_bset` macro relies on the `.into()` method to convert each element into the target type +/// of the `BTreeSet`. This means that the elements must be compatible with the `Into` trait for the +/// type `T` used in the `BTreeSet`. Also, this means that sometimes you must specify the type of collection's items. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ BTreeSet, into_bset }; +/// // BTreeSet of &str +/// let set1 : BTreeSet< &str > = into_bset!( "a", "b", "c" ); +/// +/// // BTreeSet of String +/// let set2 : BTreeSet< String > = into_bset!{ "a".to_string(), "b", "c" }; +/// +/// // With trailing comma +/// let set3 : BTreeSet< i32 > = into_bset!( 1, 2, 3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BTreeSet`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BTreeSet`. +/// +/// # Returns +/// +/// Returns a `BTreeSet` containing all the specified elements. The capacity of the set is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices: +/// +/// ```rust +/// # use collection_tools::{ BTreeSet, into_bset }; +/// let set : BTreeSet< &str > = into_bset!( "one", "two", "three" ); +/// assert!( set.contains( "one" ) ); +/// assert!( set.contains( "two" ) ); +/// assert!( set.contains( "three" ) ); +/// assert_eq!( set.len(), 3 ); +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into`: +/// +/// ```rust +/// # use collection_tools::{ BTreeSet, into_bset }; +/// let numbers : BTreeSet< i32 > = into_bset!( 1, 2, 3 ); +/// assert!( numbers.contains( &1 ) ); +/// assert!( numbers.contains( &2 ) ); +/// assert!( numbers.contains( &3 ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `BTreeSet` of `String` from string literals: +/// +/// ```rust +/// # use collection_tools::{ BTreeSet, into_bset }; +/// let s : BTreeSet< String > = into_bset!{ "value" }; +/// assert!( s.contains( "value" ) ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! into_bset +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let mut _set = collection_tools::BTreeSet::new(); + $( + _set.insert( Into::into( $key ) ); + )* + _set + }}; +} + +/// Creates a `BinaryHeap` from a list of elements. +/// +/// The `into_heap` macro simplifies the creation of a `BinaryHeap` with initial elements. +/// Elements passed to the macro are automatically converted into the heap's element type +/// using `.into()`, allowing for the use of literals or values of different, but convertible types. +/// +/// Note: The `into_heap` macro utilizes the `.into()` method to convert each element into the target type +/// of the `BinaryHeap`. This means that the elements must be compatible with the `Into` trait for the +/// type `T` used in the `BinaryHeap`. Also, this means that sometimes you must specify the type of collection's items. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ BinaryHeap, into_heap }; +/// // BinaryHeap of i32 +/// let heap1 : BinaryHeap< i32 > = into_heap!( 3, 1, 4, 1, 5, 9 ); +/// +/// // BinaryHeap of String +/// let heap2 : BinaryHeap< String > = into_heap!{ "pear".to_string(), "apple", "banana" }; +/// +/// // With trailing comma +/// let heap3 : BinaryHeap< i32 > = into_heap!( 2, 7, 1, 8, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `BinaryHeap`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `BinaryHeap`. +/// +/// # Returns +/// +/// Returns a `BinaryHeap` containing all the specified elements. The capacity of the heap is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{ BinaryHeap, into_heap }; +/// let heap : BinaryHeap< i32 > = into_heap!( 5, 3, 7, 1 ); +/// assert_eq!( heap.peek(), Some( &7 ) ); // The largest value is at the top of the heap +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into`: +/// +/// ```rust +/// # use collection_tools::{ BinaryHeap, into_heap }; +/// let chars : BinaryHeap< char > = into_heap!( 'a', 'b', 'c' ); +/// assert_eq!( chars.peek(), Some( &'c' ) ); // Characters are ordered by their ASCII value +/// ``` +/// +/// # Example +/// +/// Creating a `BinaryHeap` of `String` from string literals: +/// +/// ```rust +/// # use collection_tools::{ BinaryHeap, into_heap }; +/// let fruits : BinaryHeap< String > = into_heap!{ "cherry", "apple", "banana" }; +/// assert_eq!( fruits.peek(), Some( &"cherry".to_string() ) ); // The lexicographically largest value is at the top +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! into_heap +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _heap = collection_tools::BinaryHeap::with_capacity( _cap ); + $( + _heap.push( Into::into( $key ) ); + )* + _heap + }}; +} + +/// Creates a `HashMap` from a list of key-value pairs. +/// +/// The `into_hmap` macro allows for convenient creation of a `HashMap` with initial elements. +/// Keys and values passed to the macro are automatically converted into the map's key and value types +/// using `.into()`, enabling the use of literals or values of different, but convertible types. +/// +/// Note: The `into_hmap` macro relies on the `.into()` method to convert each key and value into the target types +/// of the `HashMap`. This means that the keys and values must be compatible with the `Into` and `Into` traits +/// for the key type `K` and value type `V` used in the `HashMap`. Also, this means that sometimes you must specify the type of collection's items. +/// +/// # Origin +/// +/// This collection can be reexported from different crates: +/// - from `std`, if `no_std` flag if off +/// - from `hashbrown`, if `use_alloc` flag if on +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of key-value pairs. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ HashMap, into_hmap }; +/// // HashMap of &str to i32 +/// let map1 : HashMap< &str, i32 > = into_hmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// +/// // HashMap of String to String +/// let map2 : HashMap< String, String > = into_hmap!{ "name".to_string() => "value".to_string(), "type" => "example" }; +/// +/// // With trailing comma +/// let map3 : HashMap< i32, &str > = into_hmap!( 1 => "one", 2 => "two", 3 => "three", ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr => $value:expr ),* $( , )?`: A comma-separated list of key-value pairs to insert into the `HashMap`. +/// Each key and value can be of any type that implements the `Into` and `Into` traits, where `K` and `V` are the +/// types stored in the `HashMap` as keys and values, respectively. +/// +/// # Returns +/// +/// Returns a `HashMap` containing all the specified key-value pairs. The capacity of the map is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices and integer values: +/// +/// ```rust +/// # use collection_tools::{ HashMap, into_hmap }; +/// let map : HashMap< &str, i32 > = into_hmap!( "one" => 1, "two" => 2, "three" => 3 ); +/// assert_eq!( map.get( "one" ), Some( &1 ) ); +/// assert_eq!( map.get( "two" ), Some( &2 ) ); +/// assert_eq!( map.get( "three" ), Some( &3 ) ); +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into` and `Into`: +/// +/// ```rust +/// # use collection_tools::{ HashMap, into_hmap }; +/// let items : HashMap< String, i32 > = into_hmap!( "pen" => 10, "book" => 45, "eraser" => 5 ); +/// assert_eq!( items.get( &"pen".to_string() ), Some(&10 ) ); +/// assert_eq!( items.get( &"book".to_string() ), Some(&45 ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `HashMap` of integers to strings from literals: +/// +/// ```rust +/// # use collection_tools::{ HashMap, into_hmap }; +/// let pairs : HashMap< i32, String > = into_hmap!( 1 => "apple", 2 => "banana" ); +/// assert_eq!( pairs.get( &1 ), Some( &"apple".to_string() ) ); +/// assert_eq!( pairs.get( &2 ), Some( &"banana".to_string() ) ); +/// ``` +/// +#[macro_export(local_inner_macros)] +macro_rules! into_hmap +{ + ( + $( $key : expr => $value : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _map = collection_tools::HashMap::with_capacity( _cap ); + $( + let _ = _map.insert( Into::into( $key ), Into::into( $value ) ); + )* + _map + }}; +} + +/// Creates a `HashSet` from a list of elements. +/// +/// The `into_hset` macro allows for convenient creation of a `HashSet` with initial elements. +/// Elements passed to the macro are automatically converted into the set's element type +/// using `.into()`, facilitating the use of literals or values of different, but convertible types. +/// +/// Note: The `into_hset` macro relies on the `.into()` method to convert each element into the target type +/// of the `HashSet`. This means that the elements must be compatible with the `Into< T >` trait for the +/// type `T` used in the `HashSet`. Also, this means that sometimes you must specify the type of collection's items. +/// +/// # Origin +/// +/// This collection can be reexported from different crates: +/// - from `std`, if `no_std` flag if off +/// - from `hashbrown`, if `use_alloc` flag if on +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ HashSet, into_hset }; +/// // HashSet of &str +/// let set1 : HashSet< &str > = into_hset!( "a", "b", "c" ); +/// +/// // HashSet of String +/// let set2 : HashSet< String > = into_hset!{ "a".to_string(), "b", "c" }; +/// +/// // With trailing comma +/// let set3 : HashSet< i32 > = into_hset!( 1, 2, 3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `HashSet`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `HashSet`. +/// +/// # Returns +/// +/// Returns a `HashSet` containing all the specified elements. The capacity of the set is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with string slices: +/// +/// ```rust +/// # use collection_tools::{ HashSet, into_hset }; +/// let set : HashSet< &str > = into_hset!( "one", "two", "three" ); +/// assert!( set.contains( "one" ) ); +/// assert!( set.contains( "two" ) ); +/// assert!( set.contains( "three" ) ); +/// assert_eq!( set.len(), 3 ); +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into< T >`: +/// +/// ```rust +/// # use collection_tools::{ HashSet, into_hset }; +/// let numbers : HashSet< i32 > = into_hset!( 1, 2, 3 ); +/// assert!( numbers.contains( &1 ) ); +/// assert!( numbers.contains( &2 ) ); +/// assert!( numbers.contains( &3 ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `HashSet` of `String` from string literals: +/// +/// ```rust +/// # use collection_tools::{ HashSet, into_hset }; +/// let s : HashSet< String > = into_hset!{ "value" }; +/// assert_eq!( s.get( "value" ), Some( &"value".to_string() ) ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! into_hset +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _set = collection_tools::HashSet::with_capacity( _cap ); + $( + let _ = _set.insert( Into::into( $key ) ); + )* + _set + }}; +} + +/// Creates a `LinkedList` from a list of elements. +/// +/// The `into_list` macro facilitates the creation of a `LinkedList` with initial elements. +/// Elements passed to the macro are automatically converted into the list's element type +/// using `.into()`, making it convenient to use literals or values of different, but convertible types. +/// +/// Note: The `into_list` macro leverages the `.into()` method to convert each element into the target type +/// of the `LinkedList`. Therefore, the elements must be compatible with the `Into` trait for the +/// type `T` used in the `LinkedList`. Also, this means that sometimes you must specify the type of collection's items. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ LinkedList, into_list }; +/// // LinkedList of i32 +/// let lst1 : LinkedList< i32 > = into_list!( 1, 2, 3, 4, 5 ); +/// +/// // LinkedList of String +/// let lst2 : LinkedList< String > = into_list!{ "hello".to_string(), "world", "rust" }; +/// +/// // With trailing comma +/// let lst3 : LinkedList< f64 > = into_list!( 1.1, 2.2, 3.3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `LinkedList`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `LinkedList`. +/// +/// # Returns +/// +/// Returns a `LinkedList` containing all the specified elements. The capacity of the list is +/// dynamically adjusted based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{ LinkedList, into_list }; +/// let lst: LinkedList< i32 > = into_list!( 1, 2, 3 ); +/// assert_eq!( lst.front(), Some( &1 ) ); // The first element is 1 +/// assert_eq!( lst.back(), Some( &3 ) ); // The last element is 3 +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into`: +/// +/// ```rust +/// # use collection_tools::{ LinkedList, into_list }; +/// let chars : LinkedList< String > = into_list!( "a", "b", "c" ); +/// assert!( chars.contains( &"a".to_string() ) ); +/// assert!( chars.contains( &"b".to_string() ) ); +/// assert!( chars.contains( &"c".to_string() ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `LinkedList` of `String` from string literals: +/// +/// ```rust +/// # use collection_tools::{ LinkedList, into_list }; +/// let fruits : LinkedList< String > = into_list!{ "apple", "banana", "cherry" }; +/// assert_eq!( fruits.front(), Some( &"apple".to_string() ) ); // The first element +/// assert_eq!( fruits.back(), Some( &"cherry".to_string() ) ); // The last element +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! into_list +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + // "The LinkedList allows pushing and popping elements at either end in constant time." + // So no `with_capacity` + let mut _lst = collection_tools::LinkedList::new(); + $( + _lst.push_back( Into::into( $key ) ); + )* + _lst + }}; +} + +/// Creates a `Vec` from a list of elements. +/// +/// The `into_vec!` macro simplifies the creation of a `Vec` with initial elements. +/// Elements passed to the macro are automatically converted into the vector's element type +/// using `.into()`, making it convenient to use literals or values of different, but convertible types. +/// +/// Note: The `into_vec!` macro utilizes the `.into()` method to convert each element into the target type +/// of the `Vec`. Therefore, the elements must be compatible with the `Into` trait for the +/// type `T` used in the `Vec`. Also, this means that sometimes you must specify the type of collection's items. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{Vec, into_vec}; +/// // Vec of i32 +/// let vec1 : Vec< i32 > = into_vec!( 1, 2, 3, 4, 5 ); +/// +/// // Vec of String +/// let vec2 : Vec< String > = into_vec!{ "hello", "world", "rust" }; +/// +/// // With trailing comma +/// let vec3 : Vec< f64 > = into_vec!( 1.1, 2.2, 3.3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key : expr ),* $( , )?`: A comma-separated list of elements to insert into the `Vec`. +/// Each element can be of any type that implements the `Into` trait, where `T` is the +/// type stored in the `Vec`. +/// +/// # Returns +/// +/// Returns a `Vec` containing all the specified elements. The capacity of the vector is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{Vec, into_vec}; +/// let vec : Vec< i32 > = into_vec!( 1, 2, 3 ); +/// assert_eq!( vec[ 0 ], 1 ); +/// assert_eq!( vec[ 1 ], 2 ); +/// assert_eq!( vec[ 2 ], 3 ); +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into`: +/// +/// ```rust +/// # use collection_tools::{Vec, into_vec}; +/// let words : Vec< String > = into_vec!( "alpha", "beta", "gamma" ); +/// assert_eq!( words[ 0 ], "alpha" ); +/// assert_eq!( words[ 1 ], "beta" ); +/// assert_eq!( words[ 2 ], "gamma" ); +/// ``` +/// +/// # Example +/// +/// Creating a `Vec` of `String` from string literals and String objects: +/// +/// ```rust +/// # use collection_tools::{Vec, into_vec}; +/// let mixed : Vec< String > = into_vec!{ "value", "another value".to_string() }; +/// assert_eq!( mixed[ 0 ], "value" ); +/// assert_eq!( mixed[ 1 ], "another value" ); +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! into_vec +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _vec = collection_tools::Vec::with_capacity( _cap ); + $( + _vec.push( Into::into( $key ) ); + )* + _vec + }}; +} + +/// Creates a `VecDeque` from a list of elements. +/// +/// The `into_vecd` macro allows for the convenient creation of a `VecDeque` with initial elements. +/// Elements passed to the macro are automatically converted into the deque's element type +/// using `.into()`, enabling the use of literals or values of different, but convertible types. +/// +/// Note: The `into_vecd` macro relies on the `.into()` method to convert each element into the target type +/// of the `VecDeque`. This means that the elements must be compatible with the `Into` trait for the +/// type `T` used in the `VecDeque`. +/// +/// # Origin +/// +/// This collection is reexported from `alloc`. +/// +/// # Syntax +/// +/// The macro can be called with a comma-separated list of elements. A trailing comma is optional. +/// +/// ```rust +/// # use collection_tools::{ VecDeque, into_vecd }; +/// // VecDeque of i32 +/// let vd1 : VecDeque< i32 > = into_vecd!( 1, 2, 3, 4, 5 ); +/// +/// // VecDeque of String +/// let vd2 : VecDeque< String > = into_vecd!{ "hello".to_string(), "world", "rust" }; +/// +/// // With trailing comma +/// let vd3 : VecDeque< f64 > = into_vecd!( 1.1, 2.2, 3.3, ); +/// ``` +/// +/// # Parameters +/// +/// - `$( $key:expr ),* $( , )?`: A comma-separated list of elements to insert into the `VecDeque`. +/// Each element can be of any type that implements the `Into< T >` trait, where `T` is the +/// type stored in the `VecDeque`. +/// +/// # Returns +/// +/// Returns a `VecDeque` containing all the specified elements. The capacity of the deque is +/// automatically determined based on the number of elements provided. +/// +/// # Example +/// +/// Basic usage with integers: +/// +/// ```rust +/// # use collection_tools::{ VecDeque, into_vecd }; +/// let vd : VecDeque< i32 > = into_vecd!( 1, 2, 3 ); +/// assert_eq!( vd.front(), Some( &1 ) ); // The first element is 1 +/// assert_eq!( vd.back(), Some( &3 ) ); // The last element is 3 +/// ``` +/// +/// # Example +/// +/// Using with different types that implement `Into< T >`: +/// +/// ```rust +/// # use collection_tools::{ VecDeque, into_vecd }; +/// let chars : VecDeque< char > = into_vecd!( 'a', 'b', 'c' ); +/// assert!( chars.contains( &'a' ) ); +/// assert!( chars.contains( &'b' ) ); +/// assert!( chars.contains( &'c' ) ); +/// ``` +/// +/// # Example +/// +/// Creating a `VecDeque` of `String` from string literals: +/// +/// ```rust +/// # use collection_tools::{ VecDeque, into_vecd }; +/// let fruits : VecDeque< String > = into_vecd!{ "apple", "banana", "cherry" }; +/// assert_eq!( fruits.front(), Some( &"apple".to_string() ) ); // The first element +/// assert_eq!( fruits.back(), Some( &"cherry".to_string() ) ); // The last element +/// ``` +/// +#[ macro_export( local_inner_macros ) ] +macro_rules! into_vecd +{ + ( + $( $key : expr ),* $( , )? + ) + => + {{ + let _cap = count!( @count $( $key ),* ); + let mut _vecd = collection_tools::VecDeque::with_capacity( _cap ); + $( + _vecd.push_back( Into::into( $key ) ); + )* + _vecd + }}; +} diff --git a/module/core/collection_tools/src/lib.rs b/module/core/collection_tools/src/lib.rs index 1c13e0ded9..220d3689a6 100644 --- a/module/core/collection_tools/src/lib.rs +++ b/module/core/collection_tools/src/lib.rs @@ -4,14 +4,41 @@ #![ doc( html_root_url = "https://docs.rs/collection_tools/latest/collection_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +// qqq : make subdirectory for each container + +// qqq : move out of lib.rs file +/// Not meant to be called directly. +#[ doc( hidden ) ] +#[ macro_export( local_inner_macros ) ] +macro_rules! count +{ + ( @single $( $x : tt )* ) => ( () ); + + ( + @count $( $rest : expr ),* + ) + => + ( + < [ () ] >::len( &[ $( count!( @single $rest ) ),* ] ) + ); +} + +/// Macros to construct the collections. +/// Basically a tweaked version of `literally` crate but using `alloc` / `hashbrown` instead of `std` +#[ cfg( all( feature = "enabled", feature = "collection_constructors" ) ) ] +pub mod constructors; + +/// Macros to construct the collections, using `.into()` under the hood. +/// Often requires explicitly specifying type to cast to. +#[ cfg( all( feature = "enabled", feature = "collection_into_constructors" ) ) ] +pub mod into_constructors; + /// Namespace with dependencies. #[ cfg( feature = "enabled" ) ] pub mod dependency { - #[ cfg( feature = "collection_constructors" ) ] - pub use ::literally; - #[ cfg( all( feature = "collection_std", feature = "use_alloc" ) ) ] + #[ cfg( feature = "use_alloc" ) ] pub use ::hashbrown; } @@ -30,32 +57,29 @@ pub mod protected #[ allow( unused_imports ) ] pub use super::orphan::*; - #[ cfg( feature = "use_alloc" ) ] + // #[ cfg( feature = "use_alloc" ) ] extern crate alloc; - #[ cfg( feature = "use_alloc" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use alloc::vec; - #[ cfg( feature = "use_alloc" ) ] + + // #[ cfg( feature = "use_alloc" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use alloc::vec::Vec; - #[ cfg( feature = "use_alloc" ) ] - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use hashbrown::*; - #[ cfg( not( feature = "no_std" ) ) ] + + // #[ cfg( feature = "use_alloc" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] - pub use std::collections::*; - #[ cfg( not( feature = "no_std" ) ) ] + pub use alloc::collections::{ BinaryHeap, BTreeMap, BTreeSet, LinkedList, VecDeque }; + + // qqq : what is comnination `use_alloc` + !`no_std` + #[ cfg( feature = "use_alloc" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] - pub use std::vec; + pub use hashbrown::{ HashMap, HashSet }; + #[ cfg( not( feature = "no_std" ) ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] - pub use std::vec::Vec; + pub use std::collections::{ HashMap, HashSet }; } @@ -81,8 +105,12 @@ pub mod exposed #[ cfg( feature = "enabled" ) ] pub mod prelude { + #[ cfg( feature = "collection_constructors" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] - #[ cfg( feature = "collection_constructors" ) ] - pub use ::literally::*; + pub use super::constructors::*; + #[ cfg( feature = "collection_into_constructors" ) ] + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::into_constructors::*; } diff --git a/module/core/collection_tools/src/vec.rs b/module/core/collection_tools/src/vec.rs new file mode 100644 index 0000000000..f4e6502089 --- /dev/null +++ b/module/core/collection_tools/src/vec.rs @@ -0,0 +1,2 @@ + +pub use core::slice::Iter diff --git a/module/core/collection_tools/tests/inc/components.rs b/module/core/collection_tools/tests/inc/components.rs new file mode 100644 index 0000000000..ee19c86a6c --- /dev/null +++ b/module/core/collection_tools/tests/inc/components.rs @@ -0,0 +1,55 @@ +#[ allow( unused_imports ) ] +use super::*; + +// + +// qqq : implement similar test for all containers +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn vec_iters() +{ + + struct MyContainer + { + entries : Vec< i32 >, + } + + impl IntoIterator for MyContainer + { + type Item = i32; + type IntoIter = std::vec::IntoIter< i32 >; + // type IntoIter = the_module::vec::IntoIter< i32 >; + // qqq : should work + + fn into_iter( self ) -> Self::IntoIter + { + self.entries.into_iter() // Create an iterator from the internal HashSet. + } + } + + impl< 'a > IntoIterator for &'a MyContainer + { + type Item = &'a i32; + type IntoIter = std::slice::Iter< 'a, i32 >; + // type IntoIter = the_module::vec::Iter< 'a, i32 >; + // qqq : should work + + fn into_iter( self ) -> Self::IntoIter + { + self.entries.iter() // Borrow the elements via an iterator. + } + } + + let instance = MyContainer { entries : vec![ 1, 2, 3 ] }; + let got : Vec< _ > = ( &instance ).into_iter().cloned().collect(); + let exp = vec![ 1, 2, 3 ]; + a_id!( got, exp ); + + let instance = MyContainer { entries : vec![ 1, 2, 3 ] }; + let got : Vec< _ > = instance.into_iter().collect(); + let exp = vec![ 1, 2, 3 ]; + a_id!( got, exp ); + +} + +// qqq : implement VectorInterface diff --git a/module/core/collection_tools/tests/inc/constructor.rs b/module/core/collection_tools/tests/inc/constructor.rs deleted file mode 100644 index 09dae06337..0000000000 --- a/module/core/collection_tools/tests/inc/constructor.rs +++ /dev/null @@ -1,64 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -// - -// #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -// #[ test ] -// fn vec() -// { -// -// // test.case( "empty" ); -// let got : std::vec::Vec< i32 > = the_module::vec!{}; -// let exp: the_module::Vec< i32 > = std::vec::Vec::new(); -// assert_eq!( got, exp ); -// -// // test.case( "single entry" ); -// let got = the_module::vec!{ 3, 13 }; -// let mut exp = std::vec::Vec::new(); -// exp.push( 3 ); -// exp.push( 13 ); -// assert_eq!( got, exp ); -// -// } - -// - -// #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -#[ test ] -fn hash_map() -{ - - // test.case( "empty" ); - let got : std::collections::HashMap< i32, i32 > = the_module::hmap!{}; - let exp = std::collections::HashMap::new(); - assert_eq!( got, exp ); - - - // test.case( "single entry" ); - let got = the_module::hmap!{ 3 => 13 }; - let mut exp = std::collections::HashMap::new(); - exp.insert( 3, 13 ); - assert_eq!( got, exp ); - -} - -// - -// #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -#[ test ] -fn hash_set() -{ - - // test.case( "empty" ); - let got : std::collections::HashSet< i32 > = the_module::hset!{}; - let exp = std::collections::HashSet::new(); - assert_eq!( got, exp ); - - // test.case( "single entry" ); - let got = the_module::hset!{ 13 }; - let mut exp = std::collections::HashSet::new(); - exp.insert( 13 ); - assert_eq!( got, exp ); - -} \ No newline at end of file diff --git a/module/core/collection_tools/tests/inc/constructors.rs b/module/core/collection_tools/tests/inc/constructors.rs new file mode 100644 index 0000000000..dda241a1a4 --- /dev/null +++ b/module/core/collection_tools/tests/inc/constructors.rs @@ -0,0 +1,171 @@ +#[ allow( unused_imports ) ] +use super::*; + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn b_tree_map() +{ + + // test.case( "empty" ); + let got : the_module::BTreeMap< i32, i32 > = the_module::bmap!{}; + let exp = the_module::BTreeMap::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::bmap!{ 3 => 13, 4 => 1 }; + let mut exp = the_module::BTreeMap::new(); + exp.insert(3, 13); + exp.insert(4, 1); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn b_tree_set() +{ + + // test.case( "empty" ); + let got : the_module::BTreeSet< i32 > = the_module::bset!{}; + let exp = the_module::BTreeSet::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::bset!{ 3, 13 }; + let mut exp = the_module::BTreeSet::new(); + exp.insert(3); + exp.insert(13); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn binary_heap() +{ + + // test.case( "empty" ); + let got : the_module::BinaryHeap< i32 > = the_module::heap!{}; + let exp: the_module::BinaryHeap< i32 > = the_module::BinaryHeap::new(); + assert_eq!( got.into_vec(), exp.into_vec() ); + + // test.case( "multiple entry" ); + let got = the_module::heap!{ 3, 13 }; + let mut exp = the_module::BinaryHeap::new(); + exp.push(3); + exp.push(13); + assert_eq!( got.into_sorted_vec(), exp.into_sorted_vec() ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn hash_map() +{ + + // test.case( "empty" ); + let got : the_module::HashMap< i32, i32 > = the_module::hmap!{}; + let exp = the_module::HashMap::new(); + assert_eq!( got, exp ); + + + // test.case( "multiple entry" ); + let got = the_module::hmap!{ 3 => 13, 4 => 1 }; + let mut exp = the_module::HashMap::new(); + exp.insert( 3, 13 ); + exp.insert( 4, 1 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn hash_set() +{ + + // test.case( "empty" ); + let got : the_module::HashSet< i32 > = the_module::hset!{}; + let exp = the_module::HashSet::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::hset!{ 13, 11 }; + let mut exp = the_module::HashSet::new(); + exp.insert( 11 ); + exp.insert( 13 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn linked_list() +{ + + // test.case( "empty" ); + let got : the_module::LinkedList< i32 > = the_module::list!{}; + let exp = the_module::LinkedList::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::list!{ 13, 15 }; + let mut exp = the_module::LinkedList::new(); + exp.push_front( 15 ); + exp.push_front( 13 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn vec() +{ + + // test.case( "empty" ); + let got : the_module::Vec< i32 > = the_module::vec!{}; + let exp = the_module::Vec::< i32 >::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::vec!{ 3, 13 }; + let mut exp = the_module::Vec::new(); + exp.push( 3 ); + exp.push( 13 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn vec_deque() +{ + + // test.case( "empty" ); + let got : the_module::VecDeque< i32 > = the_module::vecd!{}; + let exp = the_module::VecDeque::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::vecd!{ 3, 13 }; + let mut exp = the_module::VecDeque::new(); + exp.push_front( 13 ); + exp.push_front( 3 ); + assert_eq!( got, exp ); + +} diff --git a/module/core/collection_tools/tests/inc/into_constructors.rs b/module/core/collection_tools/tests/inc/into_constructors.rs new file mode 100644 index 0000000000..7423159092 --- /dev/null +++ b/module/core/collection_tools/tests/inc/into_constructors.rs @@ -0,0 +1,173 @@ +// xxx : uncomment + +#[ allow( unused_imports ) ] +use super::*; + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn b_tree_map() +{ + + // test.case( "empty" ); + let got : the_module::BTreeMap< i32, i32 > = the_module::into_bmap!{}; + let exp = the_module::BTreeMap::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::into_bmap!{ 3 => 13, 4 => 1 }; + let mut exp = the_module::BTreeMap::new(); + exp.insert(3, 13); + exp.insert(4, 1); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn b_tree_set() +{ + + // test.case( "empty" ); + let got : the_module::BTreeSet< i32 > = the_module::into_bset!{}; + let exp = the_module::BTreeSet::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::into_bset!{ 3, 13 }; + let mut exp = the_module::BTreeSet::new(); + exp.insert(3); + exp.insert(13); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn binary_heap() +{ + + // test.case( "empty" ); + let got : the_module::BinaryHeap< i32 > = the_module::into_heap!{}; + let exp = the_module::BinaryHeap::< i32 >::new(); + assert_eq!( got.into_vec(), exp.into_vec() ); + + // test.case( "multiple entry" ); + let got : the_module::BinaryHeap< i32 > = the_module::into_heap!{ 3, 13 }; + let mut exp = the_module::BinaryHeap::new(); + exp.push(3); + exp.push(13); + assert_eq!( got.into_sorted_vec(), exp.into_sorted_vec() ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn hash_map() +{ + + // test.case( "empty" ); + let got : the_module::HashMap< i32, i32 > = the_module::into_hmap!{}; + let exp = the_module::HashMap::new(); + assert_eq!( got, exp ); + + + // test.case( "multiple entry" ); + let got = the_module::into_hmap!{ 3 => 13, 4 => 1 }; + let mut exp = the_module::HashMap::new(); + exp.insert( 3, 13 ); + exp.insert( 4, 1 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn hash_set() +{ + + // test.case( "empty" ); + let got : the_module::HashSet< i32 > = the_module::into_hset!{}; + let exp = the_module::HashSet::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::into_hset!{ 13, 11 }; + let mut exp = the_module::HashSet::new(); + exp.insert( 11 ); + exp.insert( 13 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn linked_list() +{ + + // test.case( "empty" ); + let got : the_module::LinkedList< i32 > = the_module::into_list!{}; + let exp = the_module::LinkedList::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got = the_module::into_list!{ 13, 15 }; + let mut exp = the_module::LinkedList::new(); + exp.push_front( 15 ); + exp.push_front( 13 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn vec() +{ + + // test.case( "empty" ); + let got : the_module::Vec< i32 > = the_module::into_vec!{}; + let exp = the_module::Vec::< i32 >::new(); + assert_eq!( got, exp ); + + // test.case( "multiple entry" ); + let got : the_module::Vec< i32 > = the_module::into_vec!{ 3, 13 }; + let mut exp = the_module::Vec::new(); + exp.push( 3 ); + exp.push( 13 ); + assert_eq!( got, exp ); + +} + +// + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +#[ test ] +fn vec_deque() +{ + + // test.case( "empty" ); + let got : the_module::VecDeque< i32 > = the_module::into_vecd!{}; + let exp = the_module::VecDeque::new(); + assert_eq!( got, exp ); + + // test.case( "single entry" ); + let got = the_module::into_vecd!{ 3, 13 }; + let mut exp = the_module::VecDeque::new(); + exp.push_front( 13 ); + exp.push_front( 3 ); + assert_eq!( got, exp ); + +} diff --git a/module/core/collection_tools/tests/inc/mod.rs b/module/core/collection_tools/tests/inc/mod.rs index 1c63c8c58b..00cc188bc4 100644 --- a/module/core/collection_tools/tests/inc/mod.rs +++ b/module/core/collection_tools/tests/inc/mod.rs @@ -1,8 +1,16 @@ #[ allow( unused_imports ) ] use super::*; +#[ cfg( any( feature = "collection_into_constructors") ) ] +mod into_constructors; + #[ cfg( any( feature = "collection_constructors" ) ) ] -mod constructor; +mod constructors; #[ cfg( any( feature = "collection_std" ) ) ] mod reexport; + +mod components; + +// qqq : make subdirectory for each container +// qqq : don't put tests otsude of directory `inc` diff --git a/module/core/collection_tools/tests/inc/reexport.rs b/module/core/collection_tools/tests/inc/reexport.rs index aa9e756c0d..000c6bc3fd 100644 --- a/module/core/collection_tools/tests/inc/reexport.rs +++ b/module/core/collection_tools/tests/inc/reexport.rs @@ -1,36 +1,105 @@ use super::*; +// + #[ test ] #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -fn vec() +fn b_tree_map() { - let mut map : the_module::Vec< i32 > = the_module::Vec::new(); + let mut map : the_module::BTreeMap< i32, i32 > = the_module::BTreeMap::new(); + map.insert( 1, 2 ); + let exp = 2; + let got = *map.get( &1 ).unwrap(); + assert_eq!( exp, got ); +} + +// + +#[ test ] +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +fn b_tree_set() +{ + let mut map : the_module::BTreeSet< i32 > = the_module::BTreeSet::new(); + map.insert( 1 ); + assert_eq!( map.contains( &1 ), true ); + assert_eq!( map.contains( &2 ), false ); +} + +// + +#[ test ] +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +fn binary_heap() +{ + let mut map : the_module::BinaryHeap< i32 > = the_module::BinaryHeap::new(); map.push( 1 ); - map.push( 2 ); - let got = map.first().unwrap().clone(); - assert_eq!( got, 1 ); - let got = map.last().unwrap().clone(); - assert_eq!( got, 2 ); + let exp = Some(1).as_ref(); + let got = map.peek(); + assert_eq!( exp, got ); } +// + #[ test ] #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -fn hashmap() +fn hash_map() { - use the_module::HashMap; - let mut map : HashMap< i32, i32 > = HashMap::new(); + let mut map : the_module::HashMap< i32, i32 > = the_module::HashMap::new(); map.insert( 1, 2 ); let exp = 2; let got = *map.get( &1 ).unwrap(); assert_eq!( exp, got ); } +// + #[ test ] #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -fn hashset() +fn hash_set() { let mut map : the_module::HashSet< i32 > = the_module::HashSet::new(); map.insert( 1 ); assert_eq!( map.contains( &1 ), true ); assert_eq!( map.contains( &2 ), false ); } + +// + +#[ test ] +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +fn linked_list() +{ + let mut map : the_module::LinkedList< i32 > = the_module::LinkedList::new(); + map.push_back( 1 ); + assert_eq!( map.contains( &1 ), true ); + assert_eq!( map.contains( &2 ), false ); +} + +// + +#[ test ] +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +fn vec() +{ + + let mut map : the_module::Vec< i32 > = the_module::Vec::new(); + map.push( 1 ); + map.push( 2 ); + let got = map.first().unwrap().clone(); + assert_eq!( got, 1 ); + let got = map.last().unwrap().clone(); + assert_eq!( got, 2 ); + +} + +// + +#[ test ] +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +fn vec_deque() +{ + let mut map : the_module::VecDeque< i32 > = the_module::VecDeque::new(); + map.push_back( 1 ); + assert_eq!( map.contains( &1 ), true ); + assert_eq!( map.contains( &2 ), false ); +} diff --git a/module/core/collection_tools/tests/nostd/constructor.rs b/module/core/collection_tools/tests/nostd/constructor.rs deleted file mode 100644 index e4bca583ed..0000000000 --- a/module/core/collection_tools/tests/nostd/constructor.rs +++ /dev/null @@ -1,64 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -// - -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -#[ test ] -fn vec() -{ - - // test.case( "empty" ); - let got : the_module::Vec< i32 > = the_module::vec!{}; - let exp: the_module::Vec< i32 > = the_module::Vec::new(); - assert_eq!( got, exp ); - - // test.case( "single entry" ); - let got = the_module::vec!{ 3, 13 }; - let mut exp = the_module::Vec::new(); - exp.push( 3 ); - exp.push( 13 ); - assert_eq!( got, exp ); - -} - -// - -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -#[ test ] -fn hash_map() -{ - - // test.case( "empty" ); - let got : the_module::HashMap< i32, i32 > = the_module::hmap!{}; - let exp = the_module::HashMap::new(); - assert_eq!( got, exp ); - - - // test.case( "single entry" ); - let got = the_module::hmap!{ 3 => 13 }; - let mut exp = the_module::HashMap::new(); - exp.insert( 3, 13 ); - assert_eq!( got, exp ); - -} - -// - -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -#[ test ] -fn hash_set() -{ - - // test.case( "empty" ); - let got : the_module::HashSet< i32 > = the_module::hset!{}; - let exp = the_module::HashSet::new(); - assert_eq!( got, exp ); - - // test.case( "single entry" ); - let got = the_module::hset!{ 13 }; - let mut exp = the_module::HashSet::new(); - exp.insert( 13 ); - assert_eq!( got, exp ); - -} \ No newline at end of file diff --git a/module/core/collection_tools/tests/nostd/mod.rs b/module/core/collection_tools/tests/nostd/mod.rs deleted file mode 100644 index 3583433b1a..0000000000 --- a/module/core/collection_tools/tests/nostd/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -// qqq : xxx : does not work for `use_alloc`, make it working -#[ cfg( not( feature = "use_alloc" ) ) ] -#[ cfg( any( feature = "collection_constructors" ) ) ] -mod constructor; - -#[ cfg( any( feature = "collection_std" ) ) ] -mod reexport; diff --git a/module/core/collection_tools/tests/nostd/reexport.rs b/module/core/collection_tools/tests/nostd/reexport.rs deleted file mode 100644 index 19e97abb40..0000000000 --- a/module/core/collection_tools/tests/nostd/reexport.rs +++ /dev/null @@ -1,37 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -#[ test ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -fn vec() -{ - let mut map : the_module::Vec< i32 > = the_module::Vec::new(); - map.push( 1 ); - map.push( 2 ); - let got = map.first().unwrap().clone(); - assert_eq!( got, 1 ); - let got = map.last().unwrap().clone(); - assert_eq!( got, 2 ); -} - -#[ test ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -fn hashmap() -{ - use the_module::HashMap; - let mut map : HashMap< i32, i32 > = HashMap::new(); - map.insert( 1, 2 ); - let exp = 2; - let got = *map.get( &1 ).unwrap(); - assert_eq!( exp, got ); -} - -#[ test ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] -fn hashset() -{ - let mut map : the_module::HashSet< i32 > = the_module::HashSet::new(); - map.insert( 1 ); - assert_eq!( map.contains( &1 ), true ); - assert_eq!( map.contains( &2 ), false ); -} diff --git a/module/core/collection_tools/tests/nostd_tests.rs b/module/core/collection_tools/tests/nostd_tests.rs deleted file mode 100644 index deff8c0af6..0000000000 --- a/module/core/collection_tools/tests/nostd_tests.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![ cfg_attr( feature = "no_std", no_std ) ] -// tests without std - -#[ allow( unused_imports ) ] -use ::collection_tools as the_module; -// #[ allow( unused_imports ) ] -// use test_tools::exposed::*; -#[ path="../../../../module/step/meta/src/module/aggregating.rs" ] -mod aggregating; - -mod nostd; -// xxx : enable \ No newline at end of file diff --git a/module/core/collection_tools/tests/tests.rs b/module/core/collection_tools/tests/tests.rs index 103ec33039..b84a5dc030 100644 --- a/module/core/collection_tools/tests/tests.rs +++ b/module/core/collection_tools/tests/tests.rs @@ -1,11 +1,12 @@ // usual tests -#[ allow( unused_imports ) ] -use ::collection_tools as the_module; -// #[ allow( unused_imports ) ] -// use test_tools::exposed::*; #[ path="../../../../module/step/meta/src/module/aggregating.rs" ] mod aggregating; -// mod inc; -// xxx +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +#[ allow( unused_imports ) ] +use ::collection_tools as the_module; + +mod inc; diff --git a/module/core/data_type/Cargo.toml b/module/core/data_type/Cargo.toml index e582c002a6..dcbfb7b947 100644 --- a/module/core/data_type/Cargo.toml +++ b/module/core/data_type/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "data_type" -version = "0.6.0" +version = "0.7.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/data_type/Readme.md b/module/core/data_type/Readme.md index a7aa4c60f3..806e26e4c8 100644 --- a/module/core/data_type/Readme.md +++ b/module/core/data_type/Readme.md @@ -2,8 +2,7 @@ # Module :: data_type - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml)[![docs.rs](https://img.shields.io/docsrs/data_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/data_type)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fdata_type_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20data_type_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_data_type_push.yml) [![docs.rs](https://img.shields.io/docsrs/data_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/data_type) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/data_type/examples/data_type_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/data_type/examples/data_type_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of primal data types. diff --git a/module/core/data_type/examples/data_type_trivial.rs b/module/core/data_type/examples/data_type_trivial.rs new file mode 100644 index 0000000000..d3c8b6b8ab --- /dev/null +++ b/module/core/data_type/examples/data_type_trivial.rs @@ -0,0 +1,32 @@ +//! In Rust, you often need to wrap a given type into a new one. +//! The role of the orphan rules in particular is basically to prevent you from implementing external traits for external types. +//! To overcome the restriction developer usually wrap the external type into a tuple introducing a new type. +//! Type constructor does exactly that and auto-implement traits From, Into, Deref and few more for the constructed type. +//! +//! Macro [types](https://docs.rs/type_constructor/latest/type_constructor/types/macro.types.html) is responsible for generating code for Single, Pair, Homopair, Many. Each type constructor has its own keyword for that, but Pair and Homopair use the same keyword difference in a number of constituent types. It is possible to define all types at once. +fn main() +{ + #[ cfg( feature = "type_constructor" ) ] + { + use data_type::prelude::*; + + types! + { + pub single MySingle : f32; + pub single SingleWithParametrized : std::sync::Arc< T : Copy >; + pub single SingleWithParameter : < T >; + + pub pair MyPair : f32; + pub pair PairWithParametrized : std::sync::Arc< T1 : Copy >, std::sync::Arc< T2 : Copy >; + pub pair PairWithParameter : < T1, T2 >; + + pub pair MyHomoPair : f32; + pub pair HomoPairWithParametrized : std::sync::Arc< T : Copy >; + pub pair HomoPairWithParameter : < T >; + + pub many MyMany : f32; + pub many ManyWithParametrized : std::sync::Arc< T : Copy >; + pub many ManyWithParameter : < T >; + } + } +} diff --git a/module/core/data_type/examples/data_type_trivial_sample/Cargo.toml b/module/core/data_type/examples/data_type_trivial_sample/Cargo.toml deleted file mode 100644 index cf3b574c79..0000000000 --- a/module/core/data_type/examples/data_type_trivial_sample/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "data_type_trivial" -version = "0.0.0" -edition = "2021" -publish = false - -[features] -type_constructor = [] -default = [ "type_constructor" ] - -[dependencies] - data_type = { workspace = true } diff --git a/module/core/data_type/examples/data_type_trivial_sample/Readme.md b/module/core/data_type/examples/data_type_trivial_sample/Readme.md deleted file mode 100644 index c5926b9625..0000000000 --- a/module/core/data_type/examples/data_type_trivial_sample/Readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# Sample - -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=sample%2Frust%2Fdata_type_trivial,SAMPLE_FILE=.%2Fsrc%2Fmain.rs/https://github.com/Wandalen/wTools) -[![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/data_type) diff --git a/module/core/data_type/examples/data_type_trivial_sample/src/main.rs b/module/core/data_type/examples/data_type_trivial_sample/src/main.rs deleted file mode 100644 index 85a4195453..0000000000 --- a/module/core/data_type/examples/data_type_trivial_sample/src/main.rs +++ /dev/null @@ -1,26 +0,0 @@ -fn main() -{ - #[ cfg( feature = "type_constructor" ) ] - { - use data_type::prelude::*; - - types! - { - pub single MySingle : f32; - pub single SingleWithParametrized : std::sync::Arc< T : Copy >; - pub single SingleWithParameter : < T >; - - pub pair MyPair : f32; - pub pair PairWithParametrized : std::sync::Arc< T1 : Copy >, std::sync::Arc< T2 : Copy >; - pub pair PairWithParameter : < T1, T2 >; - - pub pair MyHomoPair : f32; - pub pair HomoPairWithParametrized : std::sync::Arc< T : Copy >; - pub pair HomoPairWithParameter : < T >; - - pub many MyMany : f32; - pub many ManyWithParametrized : std::sync::Arc< T : Copy >; - pub many ManyWithParameter : < T >; - } - } -} diff --git a/module/core/derive_tools/Cargo.toml b/module/core/derive_tools/Cargo.toml index cdf5b512d4..a873222be0 100644 --- a/module/core/derive_tools/Cargo.toml +++ b/module/core/derive_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "derive_tools" -version = "0.20.0" +version = "0.21.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -62,7 +62,7 @@ default = [ "derive_clone_dyn", # "derive_clone_dyn_use_std", - "strum_derive", + "derive_strum", "strum_phf", "derive_from", @@ -102,11 +102,11 @@ full = [ "derive_display", "derive_from_str", - + "derive_clone_dyn", # "derive_clone_dyn_use_std", - "strum_derive", + "derive_strum", "strum_phf", "derive_from", @@ -159,7 +159,7 @@ derive_display = [ "parse-display" ] derive_from_str = [ "parse-display", "parse-display/std", "parse-display/regex" ] # derive_from_str = [ "parse-display", "parse-display/std", "parse-display/regex", "parse-display/once_cell" ] -strum_derive = [ "strum/std", "strum/derive", "strum/strum_macros" ] +derive_strum = [ "strum/std", "strum/derive", "strum/strum_macros" ] strum_phf = [ "strum/std", "strum/phf", "strum/strum_macros" ] # zzz : review features @@ -170,13 +170,12 @@ derive_clone_dyn_use_alloc = [ "derive_clone_dyn", "clone_dyn/use_alloc" ] derive_from = [ "derive_tools_meta/derive_from" ] derive_inner_from = [ "derive_tools_meta/derive_inner_from" ] - +parse_display = [ "parse-display" ] [dependencies] ## external -# derive_more = { version = "~0.99.17", optional = true, default-features = false } -derive_more = { version = "~1.0.0-beta.6", optional = true, default-features = false } +derive_more = { version = "~1.0.0-beta.6", optional = true, default-features = false, features = [ "debug" ] } strum = { version = "~0.25", optional = true, default-features = false } # strum_macros = { version = "~0.25.3", optional = true, default-features = false } parse-display = { version = "~0.8.2", optional = true, default-features = false } diff --git a/module/core/derive_tools/Readme.md b/module/core/derive_tools/Readme.md index d6c827c5b1..99e0ba851c 100644 --- a/module/core/derive_tools/Readme.md +++ b/module/core/derive_tools/Readme.md @@ -2,8 +2,7 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/derive_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/derive_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fderive_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20derive_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/derive_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/derive_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/derive_tools/examples/derive_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/derive_tools/examples/derive_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) ### Basic use-case diff --git a/module/core/derive_tools/src/lib.rs b/module/core/derive_tools/src/lib.rs index edd8685033..3e29da3f59 100644 --- a/module/core/derive_tools/src/lib.rs +++ b/module/core/derive_tools/src/lib.rs @@ -56,7 +56,7 @@ mod derive_more // #[ cfg( feature = "derive_reflect" ) ] // pub mod reflect; -// use derive_tools_meta::Deref; +// use derive_tools_meta::Deref; // use derive_tools_meta::VariadicFrom; /// Namespace with dependencies. @@ -66,7 +66,7 @@ pub mod dependency { #[ cfg( feature = "derive_more" ) ] pub use ::derive_more; - #[ cfg( feature = "strum" ) ] + #[ cfg( feature = "derive_strum" ) ] pub use ::strum; #[ cfg( feature = "parse_display" ) ] pub use ::parse_display; @@ -125,6 +125,11 @@ pub mod exposed #[ allow( unused_imports ) ] pub use super::derive_more::*; + #[ cfg( feature = "derive_strum" ) ] + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use ::strum::*; + // #[ cfg( all( feature = "derive_more", feature = "derive_add" ) ) ] // #[ doc( inline ) ] // #[ allow( unused_imports ) ] @@ -188,7 +193,7 @@ pub mod exposed // UpperHex, // }; - #[ cfg( feature = "strum" ) ] + #[ cfg( feature = "derive_strum" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use ::strum::*; @@ -227,13 +232,17 @@ pub mod exposed #[ cfg( feature = "derive_from" ) ] pub use ::derive_tools_meta::From; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + #[ cfg( feature = "derive_inner_from" ) ] + pub use ::derive_tools_meta::InnerFrom; + } /// Prelude to use essentials: `use my_module::prelude::*`. #[ cfg( feature = "enabled" ) ] pub mod prelude { - #[ cfg( feature = "derive_clone_dyn" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] diff --git a/module/core/derive_tools/tests/inc/basic_test.rs b/module/core/derive_tools/tests/inc/basic_test.rs index 364383fa1e..f83df41abe 100644 --- a/module/core/derive_tools/tests/inc/basic_test.rs +++ b/module/core/derive_tools/tests/inc/basic_test.rs @@ -84,7 +84,7 @@ tests_impls! #[ cfg( all( feature = "strum", feature = "strum_derive" ) ) ] fn enum_with_strum() { - use the_module::*; + use the_module::{ EnumIter, IntoEnumIterator }; #[ derive( EnumIter, Debug, PartialEq ) ] enum Foo diff --git a/module/core/derive_tools/tests/tests.rs b/module/core/derive_tools/tests/tests.rs index 31961842a8..766eadcfe7 100644 --- a/module/core/derive_tools/tests/tests.rs +++ b/module/core/derive_tools/tests/tests.rs @@ -4,5 +4,6 @@ use derive_tools as the_module; #[ allow( unused_imports ) ] use test_tools::exposed::*; +#[ cfg( feature = "enabled" ) ] mod inc; diff --git a/module/core/derive_tools_meta/Cargo.toml b/module/core/derive_tools_meta/Cargo.toml index 7a3048a953..5eba0b3302 100644 --- a/module/core/derive_tools_meta/Cargo.toml +++ b/module/core/derive_tools_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "derive_tools_meta" -version = "0.17.0" +version = "0.18.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/derive_tools_meta/Readme.md b/module/core/derive_tools_meta/Readme.md index a76fa6de04..53f7fba9f0 100644 --- a/module/core/derive_tools_meta/Readme.md +++ b/module/core/derive_tools_meta/Readme.md @@ -1,8 +1,7 @@ # Module :: derive_tools_meta - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml)[![docs.rs](https://img.shields.io/docsrs/derive_tools_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/derive_tools_meta)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fderive_tools_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20derive_tools_meta_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_derive_tools_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/derive_tools_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/derive_tools_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of derives which extend STD. Its meta module. diff --git a/module/core/diagnostics_tools/Cargo.toml b/module/core/diagnostics_tools/Cargo.toml index 81860cd1a5..5053a7402c 100644 --- a/module/core/diagnostics_tools/Cargo.toml +++ b/module/core/diagnostics_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diagnostics_tools" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/diagnostics_tools/Readme.md b/module/core/diagnostics_tools/Readme.md index 578a959f16..23e15200d7 100644 --- a/module/core/diagnostics_tools/Readme.md +++ b/module/core/diagnostics_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: diagnostics_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/diagnostics_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/diagnostics_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fdiagnostics_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20diagnostics_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_diagnostics_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/diagnostics_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/diagnostics_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/diagnostics_tools/examples/diagnostics_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/diagnostics_tools/examples/diagnostics_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Diagnostics tools. diff --git a/module/core/error_tools/Cargo.toml b/module/core/error_tools/Cargo.toml index 2ef98cce7d..e1398d3d64 100644 --- a/module/core/error_tools/Cargo.toml +++ b/module/core/error_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "error_tools" -version = "0.12.0" +version = "0.13.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/error_tools/Readme.md b/module/core/error_tools/Readme.md index cef6a561ef..ca1cc00933 100644 --- a/module/core/error_tools/Readme.md +++ b/module/core/error_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: error_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/error_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/error_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ferror_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20error_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_error_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/error_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/error_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/error_tools/examples/error_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/error_tools/examples/error_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Basic exceptions handling mechanism. diff --git a/module/core/for_each/Cargo.toml b/module/core/for_each/Cargo.toml index 3671765ab3..4632085ca1 100644 --- a/module/core/for_each/Cargo.toml +++ b/module/core/for_each/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "for_each" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/for_each/Readme.md b/module/core/for_each/Readme.md index 2edba80db9..694ab59968 100644 --- a/module/core/for_each/Readme.md +++ b/module/core/for_each/Readme.md @@ -2,8 +2,7 @@ # Module :: for_each - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml)[![docs.rs](https://img.shields.io/docsrs/for_each?color=e3e8f0&logo=docs.rs)](https://docs.rs/for_each)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ffor_each_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20for_each_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_for_each_push.yml) [![docs.rs](https://img.shields.io/docsrs/for_each?color=e3e8f0&logo=docs.rs)](https://docs.rs/for_each) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/for_each/examples/for_each_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/for_each/examples/for_each_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Apply a macro for each element of a list. diff --git a/module/core/for_each/examples/for_each_trivial_sample.rs b/module/core/for_each/examples/for_each_trivial.rs similarity index 100% rename from module/core/for_each/examples/for_each_trivial_sample.rs rename to module/core/for_each/examples/for_each_trivial.rs diff --git a/module/core/former/Cargo.toml b/module/core/former/Cargo.toml index cf6e7e15f5..09489d9b38 100644 --- a/module/core/former/Cargo.toml +++ b/module/core/former/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "former" -version = "0.16.0" +version = "1.0.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -59,7 +59,7 @@ derive_from_components = [ "derive_components", "former_meta/derive_from_compone [dependencies] former_meta = { workspace = true } -collection_tools = { workspace = true, features = [ "collection_std" ] } +collection_tools = { workspace = true, features = [ "collection_std", "collection_constructors" ] } [dev-dependencies] diff --git a/module/core/former/Readme.md b/module/core/former/Readme.md index ec0a4f16ec..35aea33b92 100644 --- a/module/core/former/Readme.md +++ b/module/core/former/Readme.md @@ -2,8 +2,7 @@ # Module :: former - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml)[![docs.rs](https://img.shields.io/docsrs/former?color=e3e8f0&logo=docs.rs)](https://docs.rs/former)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fformer_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20former_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_push.yml) [![docs.rs](https://img.shields.io/docsrs/former?color=e3e8f0&logo=docs.rs)](https://docs.rs/former) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/former/examples/former_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/former/examples/former_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) A flexible and extensible implementation of the builder pattern. @@ -20,44 +19,42 @@ It offers specialized subformers for common Rust collections like `Vec`, `HashMa This approach abstracts away the need for manually implementing a builder for each struct, making code more readable and maintainable. -## Basic use-case +## Example : Trivial -The provided code snippet illustrates a basic use-case of the Former crate in Rust, which is used to apply the builder pattern for structured and flexible object creation. Below is a detailed explanation of each part of the markdown chapter, aimed at clarifying how the Former trait simplifies struct instantiation. + + + + + + + +The provided code snippet illustrates a basic use-case of the Former, which is used to apply the builder pattern for structured and flexible object creation. Below is a detailed explanation of each part of the markdown chapter, aimed at clarifying how the Former trait simplifies struct instantiation. ```rust -#[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] -fn main() -{ +# #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] +# fn main() {} + +# #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +# fn main() +# { + use former::Former; + // Use attribute debug to print expanded code. #[ derive( Debug, PartialEq, Former ) ] - #[ perform( fn greet_user() ) ] + // #[ debug ] pub struct UserProfile { - #[default(1)] age : i32, - username : String, - - #[alias(bio)] bio_optional : Option< String >, // Fields could be optional } - impl UserProfile - { - fn greet_user(self) -> Self - { - println!("Hello, {}", self.username); - self - } - } - let profile = UserProfile::former() .age( 30 ) .username( "JohnDoe".to_string() ) .bio_optional( "Software Developer".to_string() ) // Optionally provide a bio .form(); - // .perform(); // same as `form()` but will execute method passed to `perform` attribute dbg!( &profile ); // Expected output: @@ -67,228 +64,420 @@ fn main() // bio_optional: Some("Software Developer"), // } - } - ``` +# } +```
The code above will be expanded to this ```rust +# #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] +# fn main() {} -#[ derive( Debug, PartialEq ) ] -pub struct UserProfile -{ - age : i32, - username : String, - bio_optional : Option< String >, // Fields could be optional -} +# #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +# fn main() +# { -impl UserProfile -{ - #[ inline( always ) ] - pub fn former() -> UserProfileFormer< UserProfile, former::ReturnFormed > + // Use attribute debug to print expanded code. + #[ derive( Debug, PartialEq ) ] + pub struct UserProfile { - UserProfileFormer::< UserProfile, former::ReturnFormed >::new() + age : i32, + username : String, + bio_optional : Option< String >, // Fields could be optional } -} -#[ derive( Debug, Default ) ] -pub struct UserProfileFormerStorage -{ - age : Option< i32 >, - username : Option< String >, - bio_optional : Option< String >, -} + impl UserProfile + where + { + #[ inline( always ) ] + pub fn former() -> UserProfileFormer< + UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed > + > + { + UserProfileFormer::< UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed > >:: + new_coercing(former::ReturnPreformed) + } + } -pub struct UserProfileFormer -< - Context = UserProfile, - End = former::ReturnFormed, -> -where - End : former::FormingEnd< UserProfile, Context >, -{ - storage : UserProfileFormerStorage, - context : Option< Context >, - on_end : Option< End >, -} + // = entity to -impl< Context, End > UserProfileFormer< Context, End > -where - End : former::FormingEnd< UserProfile, Context >, -{ - #[ inline( always ) ] - pub fn form( mut self ) -> UserProfile + impl< Definition > former::EntityToFormer< Definition > for UserProfile + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, + { + type Former = UserProfileFormer< Definition >; + } + + impl former::EntityToStorage for UserProfile + where + { + type Storage = UserProfileFormerStorage; + } + + impl< Context, Formed, End > former::EntityToDefinition< Context, Formed, End > for UserProfile + where + End : former::FormingEnd< UserProfileFormerDefinitionTypes< Context, Formed > >, + { + type Definition = UserProfileFormerDefinition< Context, Formed, End >; + type Types = UserProfileFormerDefinitionTypes< Context, Formed >; + } + + // = definition + + #[derive(Debug)] + pub struct UserProfileFormerDefinitionTypes< Context = (), Formed = UserProfile, > + where { - let age = if self.storage.age.is_some() + _phantom : core::marker::PhantomData< (*const Context, *const Formed) >, + } + + impl< Context, Formed, > ::core::default::Default for UserProfileFormerDefinitionTypes< Context, Formed, > + where + { + fn default() -> Self { - self.storage.age.take().unwrap() + Self + { + _phantom : core::marker::PhantomData, + } } - else + } + + impl< Context, Formed, > former::FormerDefinitionTypes for UserProfileFormerDefinitionTypes< Context, Formed, > + where + { + type Storage = UserProfileFormerStorage; + type Formed = Formed; + type Context = Context; + } + + #[derive(Debug)] + pub struct UserProfileFormerDefinition< Context = (), Formed = UserProfile, End = former::ReturnPreformed, > + where + { + _phantom : core::marker::PhantomData< (*const Context, *const Formed, *const End) >, + } + + impl< Context, Formed, End, > ::core::default::Default for UserProfileFormerDefinition< Context, Formed, End, > + where + { + fn default() -> Self { - let val : i32 = + Self { - trait NotDefault< T > - { - fn maybe_default( self : &Self ) -> T { panic!( "Field 'age' isn't initialized" ) } - } - trait WithDefault< T > - { - fn maybe_default( self : &Self ) -> T; - } - impl< T > NotDefault< T > for &::core::marker::PhantomData< T > {} - impl< T > WithDefault< T > for ::core::marker::PhantomData< T > - where - T : ::core::default::Default, - { - fn maybe_default( self : &Self ) -> T - { - T::default() - } - } - ( &::core::marker::PhantomData::< i32 > ).maybe_default() - }; - val - }; - let username = if self.storage.username.is_some() + _phantom : core::marker::PhantomData, + } + } + } + + impl< Context, Formed, End, > former::FormerDefinition for UserProfileFormerDefinition< Context, Formed, End, > + where + End : former::FormingEnd< UserProfileFormerDefinitionTypes< Context, Formed, > >, + { + type Types = UserProfileFormerDefinitionTypes< Context, Formed, >; + type End = End; + type Storage = UserProfileFormerStorage; + type Formed = Formed; + type Context = Context; + } + + impl< Context, Formed, > former::FormerMutator for UserProfileFormerDefinitionTypes< Context, Formed, > + where + {} + + // = storage + + pub struct UserProfileFormerStorage + where + { + pub age : ::core::option::Option< i32 >, + pub username : ::core::option::Option< String >, + pub bio_optional : Option< String >, + } + + impl ::core::default::Default for UserProfileFormerStorage + where + { + #[ inline( always ) ] + fn default() -> Self { - self.storage.username.take().unwrap() + Self + { + age : ::core::option::Option::None, + username : ::core::option::Option::None, + bio_optional : ::core::option::Option::None, + } } - else + } + + impl former::Storage for UserProfileFormerStorage + where + { + type Preformed = UserProfile; + } + + impl former::StoragePreform for UserProfileFormerStorage + where + { + fn preform(mut self) -> Self::Preformed { - let val : String = + let age = if self.age.is_some() + { + self.age.take().unwrap() + } + else { - trait NotDefault< T > - { - fn maybe_default( self : &Self ) -> T { panic!( "Field 'username' isn't initialized" ) } - } - trait WithDefault< T > { - fn maybe_default( self : &Self ) -> T; + trait MaybeDefault< T > + { + fn maybe_default(self : &Self) -> T + { + panic!("Field 'age' isn't initialized") + } + } + impl< T > MaybeDefault< T > for &::core::marker::PhantomData< T > + {} + impl< T > MaybeDefault< T > for ::core::marker::PhantomData< T > + where T : ::core::default::Default, + { + fn maybe_default(self : &Self) -> T + { + T::default() + } + } + (&::core::marker::PhantomData::< i32 >).maybe_default() } - impl< T > NotDefault< T > for &::core::marker::PhantomData< T > {} - impl< T > WithDefault< T > for ::core::marker::PhantomData< T > - where - T : ::core::default::Default, + }; + let username = if self.username.is_some() + { + self.username.take().unwrap() + } + else + { { - fn maybe_default( self : &Self ) -> T + trait MaybeDefault< T > + { + fn maybe_default(self : &Self) -> T + { + panic!("Field 'username' isn't initialized") + } + } + impl< T > MaybeDefault< T > for &::core::marker::PhantomData< T > + {} + impl< T > MaybeDefault< T > for ::core::marker::PhantomData< T > + where T : ::core::default::Default, { - T::default() + fn maybe_default(self : &Self) -> T + { + T::default() + } } + (&::core::marker::PhantomData::< String >).maybe_default() } - ( &::core::marker::PhantomData::< String > ).maybe_default() }; - val - }; - let bio_optional = if self.storage.bio_optional.is_some() - { - Option::Some( self.storage.bio_optional.take().unwrap() ) + let bio_optional = if self.bio_optional.is_some() + { + ::core::option::Option::Some(self.bio_optional.take().unwrap()) + } + else + { + ::core::option::Option::None + }; + let result = UserProfile::<> + { + age, + username, + bio_optional, + }; + return result; } - else - { - Option::None - }; - let result = UserProfile - { - age, - username, - bio_optional, - }; - return result; } - #[ inline( always ) ] - pub fn perform( self ) -> UserProfile + pub struct UserProfileFormer< Definition = UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed >, > + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, { - let result = self.form(); - return result; + pub storage : Definition::Storage, + pub context : core::option::Option< Definition::Context >, + pub on_end : core::option::Option< Definition::End >, } - #[ inline( always ) ] - pub fn new() -> UserProfileFormer< UserProfile, former::ReturnFormed > + impl< Definition, > UserProfileFormer< Definition, > + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, Definition::Types : former::FormerDefinitionTypes< Storage = UserProfileFormerStorage >, { - UserProfileFormer::< UserProfile, former::ReturnFormed >::begin( None, former::ReturnFormed ) - } + #[ inline( always ) ] + pub fn new(on_end : Definition::End) -> Self + { + Self::begin_coercing(None, None, on_end) + } - #[ inline( always ) ] - pub fn begin - ( - context : Option< Context >, - on_end : End, - ) -> Self - { - Self + #[ inline( always ) ] + pub fn new_coercing< IntoEnd >(end : IntoEnd) -> Self + where IntoEnd : Into< Definition::End >, { - storage : core::default::Default::default(), - context : context, - on_end : Option::Some( on_end ), + Self::begin_coercing(None, None, end,) } - } - #[ inline( always ) ] - pub fn end( mut self ) -> Context - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) + #[ inline( always ) ] + pub fn begin(mut storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : ::End,) -> Self + { + if storage.is_none() + { + storage = Some(::core::default::Default::default()); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some(on_end), + } + } + + #[ inline( always ) ] + pub fn begin_coercing< IntoEnd >(mut storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : IntoEnd,) -> Self + where IntoEnd : ::core::convert::Into< ::End >, + { + if storage.is_none() + { + storage = Some(::core::default::Default::default()); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some(::core::convert::Into::into(on_end)), + } + } + + #[ inline( always ) ] + pub fn form(self) -> ::Formed + { + self.end() + } + + #[ inline( always ) ] + pub fn end(mut self) -> ::Formed + { + let on_end = self.on_end.take().unwrap(); + let mut context = self.context.take(); + ::form_mutation(&mut self.storage, &mut context); + former::FormingEnd::::call(&on_end, self.storage, context) + } + + #[ inline( always ) ] + pub fn age< Src >(mut self, src : Src) -> Self + where Src : ::core::convert::Into< i32 >, + { + debug_assert!(self.storage.age.is_none()); + self.storage.age = ::core::option::Option::Some(::core::convert::Into::into(src)); + self + } + + #[ inline( always ) ] + pub fn username< Src >(mut self, src : Src) -> Self + where Src : ::core::convert::Into< String >, + { + debug_assert!(self.storage.username.is_none()); + self.storage.username = ::core::option::Option::Some(::core::convert::Into::into(src)); + self + } + + #[ inline( always ) ] + pub fn bio_optional< Src >(mut self, src : Src) -> Self + where Src : ::core::convert::Into< String >, + { + debug_assert!(self.storage.bio_optional.is_none()); + self.storage.bio_optional = ::core::option::Option::Some(::core::convert::Into::into(src)); + self + } } - #[ inline ] - pub fn age< Src >( mut self, src : Src ) -> Self + impl< Definition, > UserProfileFormer< Definition, > where - Src : Into< i32 >, + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage, Formed = UserProfile >, { - debug_assert!( self.storage.age.is_none() ); - self.storage.age = Option::Some( src.into() ); - self + pub fn preform(self) -> ::Formed + { + former::StoragePreform::preform(self.storage) + } } - #[ inline ] - pub fn username< Src >( mut self, src : Src ) -> Self + impl< Definition, > UserProfileFormer< Definition, > where - Src : Into< String >, + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage, Formed = UserProfile, >, { - debug_assert!( self.storage.username.is_none() ); - self.storage.username = Option::Some( src.into() ); - self + #[ inline( always ) ] + pub fn perform(self) -> Definition::Formed + { + let result = self.form(); + return result; + } } - #[ inline ] - pub fn bio_optional< Src >( mut self, src : Src ) -> Self + impl< Definition > former::FormerBegin< Definition > for UserProfileFormer< Definition, > where - Src : Into< String >, + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, { - debug_assert!( self.storage.bio_optional.is_none() ); - self.storage.bio_optional = Option::Some( src.into() ); - self + #[ inline( always ) ] + fn former_begin(storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : Definition::End,) -> Self + { + debug_assert!(storage.is_none()); + Self::begin(None, context, on_end) + } } -} -let profile = UserProfile::former() -.age( 30 ) -.username( "JohnDoe".to_string() ) -.bio_optional( "Software Developer".to_string() ) -.form(); + // = as subformer -dbg!( &profile ); -// Expected output: -// &profile = UserProfile { -// age: 30, -// username: "JohnDoe", -// bio_optional: Some("Software Developer"), -// } + pub type UserProfileAsSubformer< Superformer, End > = + UserProfileFormer< UserProfileFormerDefinition< Superformer, Superformer, End, >, >; + pub trait UserProfileAsSubformerEnd< SuperFormer > + where + Self : former::FormingEnd< UserProfileFormerDefinitionTypes< SuperFormer, SuperFormer >, >, {} + + impl< SuperFormer, T > UserProfileAsSubformerEnd< SuperFormer > for T + where + Self : former::FormingEnd< UserProfileFormerDefinitionTypes< SuperFormer, SuperFormer >, >, + {} + + // = end + + let profile = UserProfile::former() + .age( 30 ) + .username( "JohnDoe".to_string() ) + .bio_optional( "Software Developer".to_string() ) // Optionally provide a bio + .form(); + dbg!( &profile ); + + // Expected output: + // + // &profile = UserProfile { + // age: 30, + // username: "JohnDoe", + // bio_optional: Some("Software Developer"), + // } + +# } ```
-### Custom and Alternative Setters +Try out `cargo run --example former_trivial`. +
+[See code](./examples/former_trivial.rs). + +## Example : Custom and Alternative Setters With help of `Former`, it is possible to define multiple versions of a setter for a single field, providing the flexibility to include custom logic within the setter methods. This feature is particularly useful when you need to preprocess data or enforce specific constraints before assigning values to fields. Custom setters should have unique names to differentiate them from the default setters generated by `Former`, allowing for specialized behavior while maintaining clarity in your code. ```rust +# #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] +# fn main() {} + # #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +# fn main() # { use former::Former; @@ -328,12 +517,20 @@ assert_eq!( example.word, "Hello!".to_string() ); In the example above showcases a custom alternative setter, `word_exclaimed`, which appends an exclamation mark to the input string before storing it. This approach allows for additional processing or validation of the input data without compromising the simplicity of the builder pattern. -### Custom Setter Overriding +Try out `cargo run --example former_custom_setter`. +
+[See code](./examples/former_custom_setter.rs). + +## Example : Custom Setter Overriding But it's also possible to completely override setter and write its own from scratch. For that use attribe `[ setter( false ) ]` to disable setter. ```rust +# #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] +# fn main() {} + # #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +# fn main() # { use former::Former; @@ -342,7 +539,7 @@ use former::Former; #[ derive( Debug, Former ) ] pub struct StructWithCustomSetters { - #[ setter( false ) ] + #[ scalar( setter = false ) ] word : String, } @@ -363,17 +560,26 @@ let example = StructWithCustomSetters::former() .word( "Hello" ) .form(); assert_eq!( example.word, "Hello!".to_string() ); + # } ``` In the example above, the default setter for `word` is disabled, and a custom setter is defined to automatically append an exclamation mark to the string. This method allows for complete control over the data assignment process, enabling the inclusion of any necessary logic or validation steps. -## Custom Default +Try out `cargo run --example former_custom_setter_overriden`. +
+[See code](./examples/former_custom_setter_overriden.rs). + +## Example : Custom Defaults -The `Former` crate enhances struct initialization in Rust by allowing the specification of custom default values for fields through the `default` attribute. This feature not only provides a way to set initial values for struct fields without relying on the `Default` trait but also adds flexibility in handling cases where a field's type does not implement `Default`, or a non-standard default value is desired. +The `Former` crate enhances struct initialization by allowing the specification of custom default values for fields through the `default` attribute. This feature not only provides a way to set initial values for struct fields without relying on the `Default` trait but also adds flexibility in handling cases where a field's type does not implement `Default`, or a non-standard default value is desired. ```rust +# #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] +# fn main() {} + # #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +# fn main() # { use former::Former; @@ -382,11 +588,11 @@ use former::Former; #[ derive( Debug, PartialEq, Former ) ] pub struct ExampleStruct { - #[ default( 5 ) ] + #[ former( default = 5 ) ] number : i32, - #[ default( "Hello, Former!".to_string() ) ] + #[ former( default = "Hello, Former!".to_string() ) ] greeting : String, - #[ default( vec![ 10, 20, 30 ] ) ] + #[ former( default = vec![ 10, 20, 30 ] ) ] numbers : Vec< i32 >, } @@ -408,6 +614,7 @@ dbg!( &instance ); // > 30, // > ], // > } + # } ``` @@ -418,200 +625,654 @@ The above code snippet showcases the `Former` crate's ability to initialize stru This approach significantly simplifies struct construction, particularly for complex types or where defaults beyond the `Default` trait's capability are required. By utilizing the `default` attribute, developers can ensure their structs are initialized safely and predictably, enhancing code clarity and maintainability. -## Concept of subformer +Try out `cargo run --example former_custom_defaults`. +
+[See code](./examples/former_custom_defaults.rs). + +## Concept of Storage and Former + +Storage is temporary storage structure holds the intermediate state of an object during its construction. + +Purpose of Storage: + +- **Intermediate State Holding**: Storage serves as a temporary repository for all the partially set properties and data of the object being formed. This functionality is essential in situations where the object's completion depends on multiple, potentially complex stages of configuration. +- **Decoupling Configuration from Instantiation**: Storage separates the accumulation of configuration states from the actual creation of the final object. This separation fosters cleaner, more maintainable code, allowing developers to apply configurations in any order and manage interim states more efficiently, without compromising the integrity of the final object. + +Storage is not just a passive container; it is an active part of a larger ecosystem that includes the former itself, a context, and a callback (often referred to as `FormingEnd`): + +- **Former as an Active Manager**: The former is responsible for managing the storage, utilizing it to keep track of the object's evolving configuration. It orchestrates the formation process by handling intermediate states and preparing the object for its final form. +- **Contextual Flexibility**: The context associated with the former adds an additional layer of flexibility, allowing the former to adjust its behavior based on the broader circumstances of the object's formation. This is particularly useful when the forming process involves conditions or states external to the object itself. +- **FormingEnd Callback**: The `FormingEnd` callback is a dynamic component that defines the final steps of the forming process. It can modify the storage based on final adjustments, validate the object's readiness, or integrate the object into a larger structure, such as embedding it as a subformer within another structure. + +These elements work in concert to ensure that the forming process is not only about building an object step-by-step but also about integrating it seamlessly into larger, more complex structures or systems. + +## Concept of Definitions + +Definitions are utilized to encapsulate and manage generic parameters efficiently and avoid passing each parameter individually. + +Two key definition Traits: + +1. **`FormerDefinitionTypes`**: + - This trait outlines the essential components involved in the formation process, including the types of storage, the form being created, and the context used. It focuses on the types involved rather than the termination of the formation process. +2. **`FormerDefinition`**: + - Building upon `FormerDefinitionTypes`, this trait incorporates the `FormingEnd` callback, linking the formation types with a definitive ending. It specifies how the formation process should conclude, which may involve validations, transformations, or integrations into larger structures. + - The inclusion of the `End` type parameter specifies the end conditions of the formation process, effectively connecting the temporary state held in storage to its ultimate form. + +## Overview of Formation Traits + +The formation process utilizes several core traits, each serving a specific purpose in the lifecycle of entity creation. These traits ensure that entities are constructed methodically, adhering to a structured pattern that enhances maintainability and scalability. Below is a summary of these key traits: -Subformers are specialized builders used within the `Former` framework to construct nested or collection-based data structures like vectors, hash maps, and hash sets. They simplify the process of adding elements to these structures by providing a fluent interface that can be seamlessly integrated into the overall builder pattern of a parent struct. This approach allows for clean and intuitive initialization of complex data structures, enhancing code readability and maintainability. +- `EntityToDefinition`: Links entities to their respective formation definitions which dictate their construction process. +- `EntityToFormer`: Connects entities with formers that are responsible for their step-by-step construction. +- `EntityToStorage`: Specifies the storage structures that temporarily hold the state of an entity during its formation. +- `FormerDefinition`, `FormerDefinitionTypes`: Define the essential properties and ending conditions of the formation process, ensuring entities are formed according to predetermined rules and logic. +- `Storage`: Establishes the fundamental interface for storage types used in the formation process, ensuring each can initialize to a default state. +- `StoragePreform`: Describes the transformation of storage from a mutable, intermediate state into the final, immutable state of the entity, crucial for accurately concluding the formation process. +- `FormerMutator`: Allows for custom mutation logic on the storage and context immediately before the formation process completes, ensuring last-minute adjustments are possible. +- `FormingEnd`: Specifies the closure action at the end of the formation process, which can transform or validate the final state of the entity. +- `FormingEndClosure`: Provides a flexible mechanism for dynamically handling the end of the formation process using closures, useful for complex scenarios. +- `FormerBegin`: Initiates a subforming process, managing how entities begin their formation in terms of storage and context setup. -### Subformer example: Building a Vector +These traits collectively facilitate a robust and flexible builder pattern that supports complex object creation and configuration scenarios. -The following example illustrates how to use a `VectorSubformer` to construct a `Vec` field within a struct. The subformer enables adding elements to the vector with a fluent interface, streamlining the process of populating collection fields within structs. +## Example : Custom Definition + +Define a custom former definition and custom forming logic, and apply them to a container. + +The example showcases how to accumulate elements into a container and then transform them into a single result using a custom `FormingEnd` implementation. This pattern is useful for scenarios where the formation process involves aggregation or transformation of input elements into a different type or form. ```rust +# #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] +# fn main() {} + # #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] -# #[ cfg( not( feature = "no_std" ) ) ] +# fn main() # { -#[ derive( Debug, PartialEq, former::Former ) ] -pub struct StructWithVec -{ - #[ subformer( former::VectorSubformer ) ] - vec : Vec< &'static str >, -} + // Define a struct `Sum` that will act as a custom former definition. + struct Sum; -let instance = StructWithVec::former() -.vec() - .push( "apple" ) - .push( "banana" ) - .end() -.form(); + // Implement `FormerDefinitionTypes` for `Sum`. + // This trait defines the types used during the forming process. + impl former::FormerDefinitionTypes for Sum + { + type Storage = Vec; // Container for the integers. + type Formed = i32; // The final type after forming, which is a single integer. + type Context = (); // No additional context is used in this example. + } + + // Implement `FormerMutator` for `Sum`. + // This trait could include custom mutation logic applied during the forming process, but it's empty in this example. + impl former::FormerMutator for Sum + { + } + + // Implement `FormerDefinition` for `Sum`. + // This trait links the custom types to the former. + impl former::FormerDefinition for Sum + { + type Types = Sum; // Associate the `FormerDefinitionTypes` with `Sum`. + type End = Sum; // Use `Sum` itself as the end handler. + type Storage = Vec; // Specify the storage type. + type Formed = i32; // Specify the final formed type. + type Context = (); // Specify the context type, not used here. + } + + // Implement `FormingEnd` for `Sum`. + // This trait handles the final step of the forming process. + impl former::FormingEnd for Sum + { + fn call + ( + &self, + storage: < Sum as former::FormerDefinitionTypes >::Storage, + _context: Option< < Sum as former::FormerDefinitionTypes >::Context> + ) + -> < Sum as former::FormerDefinitionTypes >::Formed + { + // Sum all integers in the storage vector. + storage.iter().sum() + } + } + + // Use the custom `Former` to sum a list of integers. + let got = former::ContainerFormer::::new(Sum) + .add( 1 ) // Add an integer to the storage. + .add( 2 ) // Add another integer. + .add( 10 ) // Add another integer. + .form(); // Perform the form operation, which triggers the summing logic. + let exp = 13; // Expected result after summing 1, 2, and 10. + assert_eq!(got, exp); // Assert the result is as expected. + + dbg!(got); // Debug print the result to verify the output. + // > got = 13 -assert_eq!( instance, StructWithVec { vec: vec![ "apple", "banana" ] } ); # } ``` -### Subformer example: Building a Hashmap +## Concept of subformer + +Subformers are specialized builders used within the former to construct nested or collection-based data structures like vectors, hash maps, and hash sets. They simplify the process of adding elements to these structures by providing a fluent interface that can be seamlessly integrated into the overall builder pattern of a parent struct. This approach allows for clean and intuitive initialization of complex data structures, enhancing code readability and maintainability. + +## Types of Setters / Subformers + +It's crucial to understand the differences among subform setters, container setters, and scalar setters: + +- **Scalar Setter**: Directly sets scalar values or simple fields within the forming entity. Unlike subform or container setters that manage complex objects or collections, scalar setters handle basic data types or individual fields. These are typically straightforward setter methods that do not involve nested formers or additional structuring. + +- **Container Setter**: Returns a former of the container itself, offering an interface to manage the container as a whole rather than its individual elements. This type of setter is useful for applying configurations or validations to the entire collection, such as a `HashMap` of children. -This example demonstrates the use of a `HashMapSubformer` to build a hash map within a struct. The subformer provides a concise way to insert key-value pairs into the map, making it easier to manage and construct hash map fields. +- **Subform Setter**: Returns a former of an element within a container, providing an interface to individually form each element. For example, the `child` method acts as a subform setter, allowing for the addition and configuration of individual `Child` entities within the `Parent`'s `HashMap`. + +Each type of setter is designed to address different needs in the formation process, ensuring that users can build complex, nested structures or simply set individual field values as required. + +## Example : Container Setter for a Vector + +This example demonstrates how to employ the `Former` trait to configure a `Vec` using a container setter in a structured manner. ```rust -# #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] -# #[ cfg( not( feature = "no_std" ) ) ] +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +# fn main() {} + +# #[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +# fn main() # { -use test_tools::exposed::*; + #[ derive( Debug, PartialEq, former::Former ) ] + pub struct StructWithVec + { + #[ container ] + vec : Vec< &'static str >, + } -#[ derive( Debug, PartialEq, former::Former ) ] -pub struct StructWithMap -{ - #[ subformer( former::HashMapSubformer ) ] - map : std::collections::HashMap< &'static str, &'static str >, -} + let instance = StructWithVec::former() + .vec() + .add( "apple" ) + .add( "banana" ) + .end() + .form(); + + assert_eq!( instance, StructWithVec { vec: vec![ "apple", "banana" ] } ); + dbg!( instance ); -let struct1 = StructWithMap::former() -.map() - .insert( "a", "b" ) - .insert( "c", "d" ) - .end() -.form() -; -assert_eq!( struct1, StructWithMap { map : hmap!{ "a" => "b", "c" => "d" } } ); # } ``` -### Subformer example: Building a Hashset +Try out `cargo run --example former_container_vector`. +
+[See code](./examples/former_container_vector.rs). + +## Example : Container Setter for a Hashmap -In the following example, a `HashSetSubformer` is utilized to construct a hash set within a struct. This illustrates the convenience of adding elements to a set using the builder pattern facilitated by subformers. +This example demonstrates how to effectively employ the `Former` trait to configure a `HashMap` using a container setter. ```rust -# #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] -# #[ cfg( not( feature = "no_std" ) ) ] +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +# fn main() {} + +# #[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +# fn main() # { + use collection_tools::{ HashMap, hmap }; + + #[ derive( Debug, PartialEq, former::Former ) ] + pub struct StructWithMap + { + #[ container ] + map : HashMap< &'static str, &'static str >, + } + + let instance = StructWithMap::former() + .map() + .add( ( "a", "b" ) ) + .add( ( "c", "d" ) ) + .end() + .form() + ; + assert_eq!( instance, StructWithMap { map : hmap!{ "a" => "b", "c" => "d" } } ); + dbg!( instance ); + +# } +``` + +Try out `cargo run --example former_container_hashmap`. +
+[See code](./examples/former_container_hashmap.rs). + +## Example : Container Setter for a Hashset + +This example demonstrates the use of the `Former` trait to build a `collection_tools::HashSet` through subforming. -use test_tools::exposed::*; +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +# fn main() {} -#[ derive( Debug, PartialEq, former::Former ) ] -pub struct StructWithSet +# #[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +# fn main() { - #[ subformer( former::HashSetSubformer ) ] - set : std::collections::HashSet< &'static str >, -} + use collection_tools::{ HashSet, hset }; -let instance = StructWithSet::former() -.set() - .insert("apple") - .insert("banana") - .end() -.form(); + #[ derive( Debug, PartialEq, former::Former ) ] + pub struct StructWithSet + { + #[ container ] + set : HashSet< &'static str >, + } + + let instance = StructWithSet::former() + .set() + .add( "apple" ) + .add( "banana" ) + .end() + .form(); + + assert_eq!(instance, StructWithSet { set : hset![ "apple", "banana" ] }); + dbg!( instance ); -assert_eq!(instance, StructWithSet { set : hset![ "apple", "banana" ] }); # } ``` -### Custom Subformer +Try out `cargo run --example former_container_hashset`. +
+[See code](./examples/former_container_hashset.rs). -It is possible to use former of one structure to construct field of another one and integrate it into former of the last one. +## Example : Custom Scalar Setter -The example below illustrates how to incorporate the builder pattern of one structure as a subformer in another, enabling nested struct initialization within a single fluent interface. +This example demonstrates the implementation of a scalar setter using the `Former` trait. Unlike the more complex subform and container setters shown in previous examples, this example focuses on a straightforward approach to directly set a scalar value within a parent entity. The `Parent` struct manages a `HashMap` of `Child` entities, and the scalar setter is used to set the entire `HashMap` directly. - -Example of how to use former of another structure as subformer of former of current one -function `command` integrate `CommandFormer` into `AggregatorFormer`. +The `child` function within `ParentFormer` is a custom subform setter that plays a crucial role. It uniquely employs the `ChildFormer` to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a container—each child entity in this case. ```rust -# #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +# fn main() {} + +# #[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +# fn main() # { + use collection_tools::HashMap; + use former::Former; -fn main() -{ - use std::collections::HashMap; + // Child struct with Former derived for builder pattern support + #[ derive( Debug, PartialEq, Former ) ] + // #[ debug ] + pub struct Child + { + name : String, + description : String, + } + + // Parent struct to hold children + #[ derive( Debug, PartialEq, Former ) ] + // #[ debug ] + pub struct Parent + { + // Use `hint = true` to gennerate sketch of setter. + #[ scalar( setter = false, hint = false ) ] + children : HashMap< String, Child >, + } + + impl< Definition > ParentFormer< Definition > + where + Definition : former::FormerDefinition< Storage = ParentFormerStorage >, + { + #[ inline ] + pub fn children< Src >( mut self, src : Src ) -> Self + where + Src : ::core::convert::Into< HashMap< String, Child > >, + { + debug_assert!( self.storage.children.is_none() ); + self.storage.children = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + self + } + } + + let echo = Child { name : "echo".to_string(), description : "prints all subjects and properties".to_string() }; + let exit = Child { name : "exit".to_string(), description : "just exit".to_string() }; + let mut children = HashMap::new(); + children.insert( echo.name.clone(), echo ); + children.insert( exit.name.clone(), exit ); + let ca = Parent::former() + .children( children ) + .form(); + + dbg!( &ca ); + // > &ca = Parent { + // > child: { + // > "echo": Child { + // > name: "echo", + // > description: "prints all subjects and properties", + // > }, + // > "exit": Child { + // > name: "exit", + // > description: "just exit", + // > }, + // > }, + // > } + +# } +``` + +In this example, the `Parent` struct functions as a container for multiple `Child` structs, each identified by a unique child name. The `ParentFormer` implements a custom method `child`, which serves as a subformer for adding `Child` instances into the `Parent`. + +- **Child Definition**: Each `Child` consists of a `name` and a `description`, and we derive `Former` to enable easy setting of these properties using a builder pattern. +- **Parent Definition**: It holds a collection of `Child` objects in a `HashMap`. The `#[setter(false)]` attribute is used to disable the default setter, and a custom method `child` is defined to facilitate the addition of children with specific attributes. +- **Custom Subformer Integration**: The `child` method in the `ParentFormer` initializes a `ChildFormer` with a closure that integrates the `Child` into the `Parent`'s `child` map upon completion. + +Try out `cargo run --example former_custom_scalar_setter`. +
+[See code](./examples/former_custom_scalar_setter.rs). + +## Example : Custom Container Setter + +This example demonstrates the use of container setters to manage complex nested data structures with the `Former` trait, focusing on a parent-child relationship structured around a container `HashMap`. Unlike typical builder patterns that add individual elements using subform setters, this example uses a container setter to manage the entire collection of children. + +The `child` function within `ParentFormer` is a custom subform setter that plays a crucial role. It uniquely employs the `ChildFormer` to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a container—each child entity in this case. + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +# fn main() {} + +// Ensure the example only compiles when the appropriate features are enabled. +# #[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +# fn main() +# { + use collection_tools::HashMap; use former::Former; - // Command struct with Former derived for builder pattern support + // Child struct with Former derived for builder pattern support #[ derive( Debug, PartialEq, Former ) ] - pub struct Command + // #[ debug ] + pub struct Child { name : String, description : String, } - // Aggregator struct to hold commands + // Parent struct to hold children #[ derive( Debug, PartialEq, Former ) ] - pub struct Aggregator + // #[ debug ] + pub struct Parent { - #[ setter( false ) ] - command : HashMap< String, Command >, + // Use `hint = true` to gennerate sketch of setter. + #[ scalar( setter = false, hint = false ) ] + children : HashMap< String, Child >, } - // Use CommandFormer as custom subformer for AggregatorFormer to add commands by name. - impl< Context, End > AggregatorFormer< Context, End > + impl< Definition > ParentFormer< Definition > where - End : former::FormingEnd< Aggregator, Context >, + Definition : former::FormerDefinition< Storage = ParentFormerStorage >, { - pub fn command< IntoName >( self, name : IntoName ) -> CommandFormer< Self, impl former::FormingEnd< Command, Self > > + #[ inline ] + pub fn children< Src >( mut self, src : Src ) -> Self where - IntoName: core::convert::Into< String >, + Src : ::core::convert::Into< HashMap< String, Child > >, { - let on_end = | command : Command, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if let Some( ref mut commands ) = super_former.storage.command - { - commands.insert( command.name.clone(), command ); - } - else - { - let mut commands: HashMap< String, Command > = Default::default(); - commands.insert( command.name.clone(), command ); - super_former.storage.command = Some( commands ); - } - super_former - }; - let former = CommandFormer::begin( None, Some( self ), on_end ); - former.name( name ) + debug_assert!( self.storage.children.is_none() ); + self.storage.children = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + self } } - let ca = Aggregator::former() - .command( "echo" ) + let echo = Child { name : "echo".to_string(), description : "prints all subjects and properties".to_string() }; + let exit = Child { name : "exit".to_string(), description : "just exit".to_string() }; + let mut children = HashMap::new(); + children.insert( echo.name.clone(), echo ); + children.insert( exit.name.clone(), exit ); + let ca = Parent::former() + .children( children ) + .form(); + + dbg!( &ca ); + // > &ca = Parent { + // > child: { + // > "echo": Child { + // > name: "echo", + // > description: "prints all subjects and properties", + // > }, + // > "exit": Child { + // > name: "exit", + // > description: "just exit", + // > }, + // > }, + // > } + +# } +``` + +Try out `cargo run --example former_custom_container_setter`. +
+[See code](./examples/former_custom_container_setter.rs). + +## Example : Custom Subform Setter + +This example illustrates the implementation of nested builder patterns using the `Former` trait, emphasizing a parent-child relationship. Here, the `Parent` struct utilizes `ChildFormer` as a custom subformer to dynamically manage its `child` field—a `HashMap`. Each child in the `HashMap` is uniquely identified and configured via the `ChildFormer`. + +The `child` function within `ParentFormer` is a custom subform setter that plays a crucial role. It uniquely employs the `ChildFormer` to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a container—each child entity in this case. + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +# fn main() {} + +# // Ensure the example only compiles when the appropriate features are enabled. +# #[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +# fn main() +# { + use collection_tools::HashMap; + use former::Former; + + // Child struct with Former derived for builder pattern support + #[ derive( Debug, PartialEq, Former ) ] + // #[ debug ] + pub struct Child + { + name : String, + description : String, + } + + // Parent struct to hold children + #[ derive( Debug, PartialEq, Former ) ] + // #[ debug ] + pub struct Parent + { + // Use `hint = true` to gennerate sketch of setter. + #[ subform( setter = false, hint = false ) ] + child : HashMap< String, Child >, + } + + /// Initializes and configures a subformer for adding named child entities. This method leverages an internal function + /// to create and return a configured subformer instance. It allows for the dynamic addition of children with specific names, + /// integrating them into the formation process of the parent entity. + /// + impl< Definition > ParentFormer< Definition > + where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, + { + + #[ inline( always ) ] + pub fn child( self, name : &str ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._child_add::< ChildFormer< _ >, _, >() + .name( name ) + } + + } + + // Required to define how `value` is converted into pair `( key, value )` + impl former::ValToEntry< HashMap< String, Child > > for Child + { + type Entry = ( String, Child ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.name.clone(), self ) + } + } + + let ca = Parent::former() + .child( "echo" ) .description( "prints all subjects and properties" ) // sets additional properties using custom subformer .end() - .command( "exit" ) + .child( "exit" ) .description( "just exit" ) // Sets additional properties using using custom subformer .end() .form(); dbg!( &ca ); - // > &ca = Aggregator { - // > command: { - // > "echo": Command { + // > &ca = Parent { + // > child: { + // > "echo": Child { // > name: "echo", // > description: "prints all subjects and properties", // > }, - // > "exit": Command { + // > "exit": Child { // > name: "exit", // > description: "just exit", // > }, // > }, // > } -} # } ``` -In this example, the `Aggregator` struct functions as a container for multiple `Command` structs, each identified by a unique command name. The `AggregatorFormer` implements a custom method `command`, which serves as a subformer for adding `Command` instances into the `Aggregator`. +Try out `cargo run --example former_custom_subform_setter`. +
+[See code](./examples/former_custom_subform_setter.rs). + +## General Container Interface + +There are suite of traits designed to abstract and enhance the functionality of container data structures within the forming process. These traits are integral to managing the complexity of container operations, such as adding, modifying, and converting between different representations within containers like vectors, hash maps, etc. They are especially useful when used in conjunction with the `container` attribute in the `former` macro, which automates the implementation of these traits to create robust and flexible builder patterns for complex data structures. + +- [`Container`] - Defines basic functionalities for containers, managing entries and values, establishing the fundamental operations required for any custom container implementation in forming processes. +- [`EntryToVal`] - Facilitates the conversion of container entries to their value representations, crucial for operations that treat container elements more abstractly as values. +- [`ValToEntry`] - Provides the reverse functionality of `EntryToVal`, converting values back into entries, which is essential for operations that require adding or modifying entries in the container based on value data. +- [`ContainerAdd`] - Adds functionality for inserting entries into a container, considering container-specific rules such as duplication handling and order preservation, enhancing the usability of containers in forming scenarios. +- [`ContainerAssign`] - Extends the container functionality to replace all existing entries with new ones, enabling bulk updates or complete resets of container contents, which is particularly useful in dynamic data environments. + +## Custom Container Former + +Container interface is defined in the crate and implemented for containers like vectors, hash maps, etc, but if you want to use non-standard container you can implement container interface for the container. This example demonstrate how to do that. + +Try out `cargo run --example former_custom_container`. +
+[See code](./examples/former_custom_container.rs). + +## Concept of Mutator + +Provides a mechanism for mutating the context and storage just before the forming process is completed. + +The `FormerMutator` trait allows for the implementation of custom mutation logic on the internal state +of an entity (context and storage) just before the final forming operation is completed. This mutation +occurs immediately before the `FormingEnd` callback is invoked. + +Use cases of Mutator + +- Applying last-minute changes to the data being formed. +- Setting or modifying properties that depend on the final state of the storage or context. +- Storage-specific fields which are not present in formed structure. + +## Storage-Specific Fields + +Storage-specific fields are intermediate fields that exist only in the storage structure during +the forming process. These fields are not present in the final formed structure but are instrumental +in complex forming operations, such as conditional mutations, temporary state tracking, or accumulations. + +These fields are used to manage intermediate data or state that aids in the construction +of the final object but does not necessarily have a direct representation in the object's schema. For +instance, counters, flags, or temporary computation results that determine the final state of the object. + +The `FormerMutator` trait facilitates the implementation of custom mutation logic. It acts on the internal +state (context and storage) just before the final forming operation is completed, right before the `FormingEnd` +callback is invoked. This trait is crucial for making last-minute adjustments or computations based on the +accumulated state in the storage. + +## Mutator vs `FormingEnd` + +Unlike `FormingEnd`, which is responsible for integrating and finalizing the formation process of a field within +a parent former, `form_mutation` directly pertains to the entity itself. This method is designed to be independent +of whether the forming process is occurring within the context of a superformer or if the structure is a standalone +or nested field. This makes `form_mutation` suitable for entity-specific transformations that should not interfere +with the hierarchical forming logic managed by `FormingEnd`. + +## Example : Mutator and Storage Fields + +This example illustrates how to use the `FormerMutator` trait for implementing custom mutations +and demonstrates the concept of storage-specific fields in the forming process. + +In this example, the fields `a` and `b` are defined only within the storage and used +within the custom mutator to enrich or modify the field `c` of the formed entity. This approach +allows for a richer and more flexible formation logic that can adapt based on the intermediate state +held within the storage. + +```rust +# #[ cfg( not( all( feature = "enabled", feature = "derive_former" ) ) ) ] +# fn main() {} + +# #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +# fn main() +# { + + use former::Former; + + #[ derive( Debug, PartialEq, Former ) ] + #[ storage_fields( a : i32, b : Option< String > ) ] + #[ mutator( custom = true ) ] + pub struct Struct1 + { + c : String, + } + + // = former mutator + + impl< Context, Formed > former::FormerMutator + for Struct1FormerDefinitionTypes< Context, Formed > + { + /// Mutates the context and storage of the entity just before the formation process completes. + #[ inline ] + fn form_mutation( storage : &mut Self::Storage, _context : &mut ::core::option::Option< Self::Context > ) + { + storage.a.get_or_insert_with( Default::default ); + storage.b.get_or_insert_with( Default::default ); + storage.c = Some( format!( "{:?} - {}", storage.a.unwrap(), storage.b.as_ref().unwrap() ) ); + } + } + + let got = Struct1::former().a( 13 ).b( "abc" ).c( "def" ).form(); + let exp = Struct1 + { + c : "13 - abc".to_string(), + }; + assert_eq!( got, exp ); + dbg!( got ); + // > got = Struct1 { + // > c: "13 - abc", + // > } + +# } +``` + +Try out `cargo run --example former_custom_mutator`. +
+[See code](./examples/former_custom_mutator.rs). + +## Index of Examples + + + + + + -- **Command Definition**: Each `Command` consists of a `name` and a `description`, and we derive `Former` to enable easy setting of these properties using a builder pattern. -- **Aggregator Definition**: It holds a collection of `Command` objects in a `HashMap`. The `#[setter(false)]` attribute is used to disable the default setter, and a custom method `command` is defined to facilitate the addition of commands with specific attributes. -- **Custom Subformer Integration**: The `command` method in the `AggregatorFormer` initializes a `CommandFormer` with a closure that integrates the `Command` into the `Aggregator`'s `command` map upon completion. +- [Custom Defaults](./examples/former_custom_defaults.rs) - Former allows the specification of custom default values for fields through the `former( default )` attribute. +- [Custom Definition](./examples/former_custom_definition.rs) - Define a custom former definition and custom forming logic, and apply them to a container. -This pattern of using a structure's former as a subformer within another facilitates the creation of deeply nested or complex data structures through a coherent and fluent interface, showcasing the powerful capabilities of the `Former` framework for Rust applications. + -### To add to your project +## To add to your project ```sh cargo add former ``` -### Try out from the repository +## Try out from the repository ```sh git clone https://github.com/Wandalen/wTools diff --git a/module/core/former/examples/former_container_hashmap.rs b/module/core/former/examples/former_container_hashmap.rs new file mode 100644 index 0000000000..8908adc7ab --- /dev/null +++ b/module/core/former/examples/former_container_hashmap.rs @@ -0,0 +1,29 @@ +//! +//! This example demonstrates how to effectively employ the `Former` trait to configure a `HashMap` using a container setter. +//! + +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + use collection_tools::{ HashMap, hmap }; + + #[ derive( Debug, PartialEq, former::Former ) ] + pub struct StructWithMap + { + #[ container ] + map : HashMap< &'static str, &'static str >, + } + + let instance = StructWithMap::former() + .map() + .add( ( "a", "b" ) ) + .add( ( "c", "d" ) ) + .end() + .form() + ; + assert_eq!( instance, StructWithMap { map : hmap!{ "a" => "b", "c" => "d" } } ); + dbg!( instance ); + +} diff --git a/module/core/former/examples/former_container_hashset.rs b/module/core/former/examples/former_container_hashset.rs new file mode 100644 index 0000000000..2c76d24a0a --- /dev/null +++ b/module/core/former/examples/former_container_hashset.rs @@ -0,0 +1,29 @@ +//! +//! This example demonstrates the use of the `Former` trait to build a `collection_tools::HashSet` through subforming. +//! + +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + use collection_tools::{ HashSet, hset }; + + #[ derive( Debug, PartialEq, former::Former ) ] + pub struct StructWithSet + { + #[ container ] + set : HashSet< &'static str >, + } + + let instance = StructWithSet::former() + .set() + .add( "apple" ) + .add( "banana" ) + .end() + .form(); + + assert_eq!(instance, StructWithSet { set : hset![ "apple", "banana" ] }); + dbg!( instance ); + +} diff --git a/module/core/former/examples/former_container_vector.rs b/module/core/former/examples/former_container_vector.rs new file mode 100644 index 0000000000..92f67dbd47 --- /dev/null +++ b/module/core/former/examples/former_container_vector.rs @@ -0,0 +1,28 @@ +//! +//! This example demonstrates how to employ the `Former` trait to configure a `Vec` using a container setter in a structured manner. +//! + +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + + #[ derive( Debug, PartialEq, former::Former ) ] + pub struct StructWithVec + { + #[ container ] + vec : Vec< &'static str >, + } + + let instance = StructWithVec::former() + .vec() + .add( "apple" ) + .add( "banana" ) + .end() + .form(); + + assert_eq!( instance, StructWithVec { vec: vec![ "apple", "banana" ] } ); + dbg!( instance ); + +} diff --git a/module/core/former/examples/former_custom_container.rs b/module/core/former/examples/former_custom_container.rs new file mode 100644 index 0000000000..c3e9ff0f8b --- /dev/null +++ b/module/core/former/examples/former_custom_container.rs @@ -0,0 +1,278 @@ +//! Example former_custom_container.rs +//! +//! This example demonstrates how to define and use a custom container with former. +//! The custom container implemented here is a `LoggingSet`, which extends the basic `HashSet` behavior +//! by logging each addition. This example illustrates how to integrate such custom containers with the +//! Former trait system for use in structured data types. + +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + use collection_tools::HashSet; + + // Custom container that logs additions. + #[ derive( Debug, PartialEq ) ] + pub struct LoggingSet< K > + where + K : core::cmp::Eq + core::hash::Hash, + { + set : HashSet< K >, // Internal HashSet to store the elements. + } + + // Implement default for the custom container. + impl< K > Default for LoggingSet< K > + where + K : core::cmp::Eq + core::hash::Hash, + { + #[ inline( always ) ] + fn default() -> Self + { + Self + { + set : Default::default() // Initialize the internal HashSet. + } + } + } + + // Allow the custom container to be converted into an iterator, to iterate over the elements. + impl< K > IntoIterator for LoggingSet< K > + where + K : std::cmp::Eq + std::hash::Hash, + { + type Item = K; + type IntoIter = std::collections::hash_set::IntoIter< K >; + + fn into_iter( self ) -> Self::IntoIter + { + self.set.into_iter() // Create an iterator from the internal HashSet. + } + } + + // Similar iterator functionality but for borrowing the elements. + impl<'a, K> IntoIterator for &'a LoggingSet< K > + where + K : std::cmp::Eq + std::hash::Hash, + { + type Item = &'a K; + type IntoIter = std::collections::hash_set::Iter< 'a, K >; + + fn into_iter( self ) -> Self::IntoIter + { + self.set.iter() // Borrow the elements via an iterator. + } + } + + // Implement the Container trait to integrate with the former system. + impl< K > former::Container for LoggingSet< K > + where + K : core::cmp::Eq + core::hash::Hash, + { + type Entry = K; + type Val = K; + + #[ inline( always ) ] + fn entry_to_val( e : Self::Entry ) -> Self::Val + { + e // Direct mapping of entries to values. + } + } + + // Implement ContainerAdd to handle adding elements to the custom container. + impl< K > former::ContainerAdd for LoggingSet< K > + where + K : core::cmp::Eq + core::hash::Hash, + { + #[ inline( always ) ] + fn add( &mut self, e : Self::Entry ) -> bool + { + self.set.insert( e ) // Log the addition and add the element to the internal HashSet. + } + } + + // Implement ContainerAssign to handle bulk assignment of elements. + impl< K > former::ContainerAssign for LoggingSet< K > + where + K : core::cmp::Eq + core::hash::Hash, + { + fn assign< Elements >( &mut self, elements : Elements ) -> usize + where + Elements : IntoIterator< Item = Self::Entry > + { + let initial_len = self.set.len(); + self.set.extend( elements ); // Extend the set with a collection of elements. + self.set.len() - initial_len // Return the number of elements added. + } + } + + // Implement ContainerValToEntry to convert values back to entries. + impl< K > former::ContainerValToEntry< K > for LoggingSet< K > + where + K : core::cmp::Eq + core::hash::Hash, + { + type Entry = K; + #[ inline( always ) ] + fn val_to_entry( val : K ) -> Self::Entry + { + val // Direct conversion of value to entry. + } + } + + // = storage + + // Define storage behavior for the custom container. + impl< K > former::Storage + for LoggingSet< K > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + { + type Preformed = LoggingSet< K >; // Define the type after the forming process. + } + + // Implement the preforming behavior to finalize the storage. + impl< K > former::StoragePreform + for LoggingSet< K > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + { + fn preform( self ) -> Self::Preformed + { + self // Return the container as is. + } + } + + // = definition types + + // Definitions related to the type settings for the LoggingSet, which detail how the container should behave with former. + + /// Holds generic parameter types for forming operations related to `LoggingSet`. + #[ derive( Debug, Default ) ] + pub struct LoggingSetDefinitionTypes< K, Context = (), Formed = LoggingSet< K > > + { + _phantom : core::marker::PhantomData< ( K, Context, Formed ) >, // PhantomData is used to handle generic parameters safely. + } + + /// Specifies the storage, formed type, and context for the `LoggingSet` when used in a forming process. + impl< K, Context, Formed > former::FormerDefinitionTypes + for LoggingSetDefinitionTypes< K, Context, Formed > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + { + type Storage = LoggingSet< K >; // Specifies that `LoggingSet` is used as the storage. + type Formed = Formed; // The final formed type after the forming process. + type Context = Context; // The context required for forming, can be specified by the user. + } + + // = definition + + /// Provides a complete definition for `LoggingSet` including the end condition of the forming process. + #[ derive( Debug, Default ) ] + pub struct LoggingSetDefinition< K, Context = (), Formed = LoggingSet< K >, End = former::ReturnStorage > + { + _phantom : core::marker::PhantomData< ( K, Context, Formed, End ) >, + } + + /// Associates the `LoggingSet` with a specific forming process and defines its behavior. + impl< K, Context, Formed, End > former::FormerDefinition + for LoggingSetDefinition< K, Context, Formed, End > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : former::FormingEnd< LoggingSetDefinitionTypes< K, Context, Formed > >, + { + type Storage = LoggingSet< K >; // The storage type during the formation process. + type Formed = Formed; // The type resulting from the formation process. + type Context = Context; // The context used during the formation process. + type Types = LoggingSetDefinitionTypes< K, Context, Formed >; // The associated type settings. + type End = End; // The ending condition for the forming process. + } + + // = mutator + + /// Optional: Implements mutating capabilities to modify the forming process of `LoggingSet` if needed. + impl< K, Context, Formed > former::FormerMutator + for LoggingSetDefinitionTypes< K, Context, Formed > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + { + } + + // = Entity To + + /// Associates the `LoggingSet` with a specific `Former` for use in forming processes. + impl< K, Definition > former::EntityToFormer< Definition > for LoggingSet< K > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + Definition : former::FormerDefinition + < + Storage = LoggingSet< K >, + Types = LoggingSetDefinitionTypes + < + K, + < Definition as former::FormerDefinition >::Context, + < Definition as former::FormerDefinition >::Formed, + >, + >, + Definition::End : former::FormingEnd< Definition::Types >, + { + type Former = LoggingSetAsSubformer< K, Definition::Context, Definition::Formed, Definition::End >; + } + + /// Specifies the storage for `LoggingSet`. + impl< K > former::EntityToStorage + for LoggingSet< K > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + { + type Storage = LoggingSet< K >; + } + + /// Defines the relationship between `LoggingSet` and its formal definition within the forming system. + impl< K, Context, Formed, End > former::EntityToDefinition< Context, Formed, End > + for LoggingSet< K > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : former::FormingEnd< LoggingSetDefinitionTypes< K, Context, Formed > >, + { + type Definition = LoggingSetDefinition< K, Context, Formed, End >; + type Types = LoggingSetDefinitionTypes< K, Context, Formed >; + } + + /// Provides type-specific settings for the formation process related to `LoggingSet`. + impl< K, Context, Formed > former::EntityToDefinitionTypes< Context, Formed > + for LoggingSet< K > + where + K : ::core::cmp::Eq + ::core::hash::Hash, + { + type Types = LoggingSetDefinitionTypes< K, Context, Formed >; + } + + // = subformer + + // Subformer type alias simplifies the usage of `ContainerFormer` with `LoggingSet`. + pub type LoggingSetAsSubformer< K, Context, Formed, End > = + former::ContainerFormer::< K, LoggingSetDefinition< K, Context, Formed, End > >; + + // == use custom container + + /// Parent required for the template. + #[ derive( Debug, Default, PartialEq, former::Former ) ] + pub struct Parent + { + #[ container ] + children : LoggingSet< i32 >, + } + + // Using the builder pattern provided by Former to manipulate Parent + let parent = Parent::former() + .children() + .add(10) + .add(20) + .add(10) + .end() + .form(); + + println!("Got: {:?}", parent); + // > Parent { children: LoggingSet { set: {10, 20} } } + +} diff --git a/module/core/former/examples/former_custom_container_setter.rs b/module/core/former/examples/former_custom_container_setter.rs new file mode 100644 index 0000000000..d5565a0908 --- /dev/null +++ b/module/core/former/examples/former_custom_container_setter.rs @@ -0,0 +1,91 @@ +// Example former_custom_container_setter.rs + +//! +//! This example demonstrates the use of container setters to manage complex nested data structures with the `Former` trait, focusing on a parent-child relationship structured around a container `HashMap`. Unlike typical builder patterns that add individual elements using subform setters, this example uses a container setter to manage the entire collection of children. +//! +//! The `child` function within `ParentFormer` is a custom subform setter that plays a crucial role. It uniquely employs the `ChildFormer` to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a container—each child entity in this case. +//! +//! #### Types of Setters +//! +//! It's crucial to understand the differences among subform setters, container setters, and scalar setters: +//! +//! - **Scalar Setter**: Directly sets scalar values or simple fields within the forming entity. Unlike subform or container setters that manage complex objects or collections, scalar setters handle basic data types or individual fields. These are typically straightforward setter methods that do not involve nested formers or additional structuring. +//! +//! - **Container Setter**: Returns a former of the container itself, offering an interface to manage the container as a whole rather than its individual elements. This type of setter is useful for applying configurations or validations to the entire collection, such as a `HashMap` of children. +//! +//! - **Subform Setter**: Returns a former of an element within a container, providing an interface to individually form each element. For example, the `child` method acts as a subform setter, allowing for the addition and configuration of individual `Child` entities within the `Parent`'s `HashMap`. +//! +//! Each type of setter is designed to address different needs in the formation process, ensuring that users can build complex, nested structures or simply set individual field values as required. +//! + +// Ensure the example only compiles when the appropriate features are enabled. +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + use collection_tools::HashMap; + use former::Former; + + // Child struct with Former derived for builder pattern support + #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Child + { + name : String, + description : String, + } + + // Parent struct to hold children + #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Parent + { + // Use `hint = true` to gennerate sketch of setter. + #[ container( setter = false, hint = false ) ] + children : HashMap< String, Child >, + } + + /// The containr setter provides a container setter that returns a ContainerFormer tailored for managing a collection of child entities. It employs a generic container definition to facilitate operations on the entire collection, such as adding or updating elements. + impl< Definition, > ParentFormer< Definition, > + where + Definition : former::FormerDefinition< Storage = ParentFormerStorage >, + { + + #[ inline( always ) ] + pub fn children( self ) -> former::ContainerFormer:: + < + ( String, Child ), + former::HashMapDefinition< String, Child, Self, Self, ParentFormerAssignChildrenEnd< Definition >, > + > + { + self._children_container_former() + } + + } + + let echo = Child { name : "echo".to_string(), description : "prints all subjects and properties".to_string() }; + let exit = Child { name : "exit".to_string(), description : "just exit".to_string() }; + let ca = Parent::former() + .children() + .add( ( echo.name.clone(), echo ) ) + .add( ( exit.name.clone(), exit ) ) + .end() + .form(); + + dbg!( &ca ); + // > &ca = Parent { + // > child: { + // > "echo": Child { + // > name: "echo", + // > description: "prints all subjects and properties", + // > }, + // > "exit": Child { + // > name: "exit", + // > description: "just exit", + // > }, + // > }, + // > } +} diff --git a/module/core/former/examples/former_custom_default.rs b/module/core/former/examples/former_custom_defaults.rs similarity index 80% rename from module/core/former/examples/former_custom_default.rs rename to module/core/former/examples/former_custom_defaults.rs index 2cc73f3fc0..e7f8e779d7 100644 --- a/module/core/former/examples/former_custom_default.rs +++ b/module/core/former/examples/former_custom_defaults.rs @@ -1,7 +1,10 @@ -//! The `Former` crate enhances struct initialization in Rust by allowing the specification of custom default values for fields through the `default` attribute. + +//! ## Example : Custom Defaults +//! +//! Former allows the specification of custom default values for fields through the `former( default )` attribute. //! //! This feature not only provides a way to set initial values for struct fields without relying on the `Default` trait but also adds flexibility in handling cases where a field's type does not implement `Default`, or a non-standard default value is desired. -//! The above code snippet showcases the `Former` crate's ability to initialize struct fields with custom default values: +//! The example showcases the `Former` crate's ability to initialize struct fields with custom default values: //! - The `number` field is initialized to `5`. //! - The `greeting` field defaults to a greeting message, "Hello, Former!". //! - The `numbers` field starts with a vector containing the integers `10`, `20`, and `30`. @@ -21,11 +24,11 @@ fn main() #[ derive( Debug, PartialEq, Former ) ] pub struct ExampleStruct { - #[ default( 5 ) ] + #[ former( default = 5 ) ] number : i32, - #[ default( "Hello, Former!".to_string() ) ] + #[ former( default = "Hello, Former!".to_string() ) ] greeting : String, - #[ default( vec![ 10, 20, 30 ] ) ] + #[ former( default = vec![ 10, 20, 30 ] ) ] numbers : Vec< i32 >, } diff --git a/module/core/former/examples/former_custom_definition.rs b/module/core/former/examples/former_custom_definition.rs new file mode 100644 index 0000000000..40957cf3ce --- /dev/null +++ b/module/core/former/examples/former_custom_definition.rs @@ -0,0 +1,72 @@ +//! ## Example : Custom Definition +//! +//! Define a custom former definition and custom forming logic, and apply them to a container. +//! +//! The example showcases how to accumulate elements into a container and then transform them into a single result +//! using a custom `FormingEnd` implementation. This pattern is useful for scenarios where the formation process +//! involves aggregation or transformation of input elements into a different type or form. + +#[ cfg( not( all( feature = "derive_former", feature = "enabled" ) ) ) ] +fn main() {} + +#[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +fn main() +{ + // Define a struct `Sum` that will act as a custom former definition. + struct Sum; + + // Implement `FormerDefinitionTypes` for `Sum`. + // This trait defines the types used during the forming process. + impl former::FormerDefinitionTypes for Sum + { + type Storage = Vec; // Container for the integers. + type Formed = i32; // The final type after forming, which is a single integer. + type Context = (); // No additional context is used in this example. + } + + // Implement `FormerMutator` for `Sum`. + // This trait could include custom mutation logic applied during the forming process, but it's empty in this example. + impl former::FormerMutator for Sum + { + } + + // Implement `FormerDefinition` for `Sum`. + // This trait links the custom types to the former. + impl former::FormerDefinition for Sum + { + type Types = Sum; // Associate the `FormerDefinitionTypes` with `Sum`. + type End = Sum; // Use `Sum` itself as the end handler. + type Storage = Vec; // Specify the storage type. + type Formed = i32; // Specify the final formed type. + type Context = (); // Specify the context type, not used here. + } + + // Implement `FormingEnd` for `Sum`. + // This trait handles the final step of the forming process. + impl former::FormingEnd for Sum + { + fn call + ( + &self, + storage: < Sum as former::FormerDefinitionTypes >::Storage, + _context: Option< < Sum as former::FormerDefinitionTypes >::Context> + ) + -> < Sum as former::FormerDefinitionTypes >::Formed + { + // Sum all integers in the storage vector. + storage.iter().sum() + } + } + + // Use the custom `Former` to sum a list of integers. + let got = former::ContainerFormer::::new(Sum) + .add( 1 ) // Add an integer to the storage. + .add( 2 ) // Add another integer. + .add( 10 ) // Add another integer. + .form(); // Perform the form operation, which triggers the summing logic. + let exp = 13; // Expected result after summing 1, 2, and 10. + assert_eq!(got, exp); // Assert the result is as expected. + + dbg!(got); // Debug print the result to verify the output. + // > got = 13 +} diff --git a/module/core/former/examples/former_custom_mutator.rs b/module/core/former/examples/former_custom_mutator.rs new file mode 100644 index 0000000000..5d83fe77c9 --- /dev/null +++ b/module/core/former/examples/former_custom_mutator.rs @@ -0,0 +1,76 @@ +// former_custom_mutator.rs + +//! This example illustrates how to use the `FormerMutator` trait for implementing custom mutations +//! and demonstrates the concept of storage-specific fields in the forming process. +//! +//! #### Storage-Specific Fields +//! +//! Storage-specific fields are intermediate fields that exist only in the storage structure during +//! the forming process. These fields are not present in the final formed structure but are instrumental +//! in complex forming operations, such as conditional mutations, temporary state tracking, or accumulations. +//! +//! These fields are used to manage intermediate data or state that aids in the construction +//! of the final object but does not necessarily have a direct representation in the object's schema. For +//! instance, counters, flags, or temporary computation results that determine the final state of the object. +//! +//! The `FormerMutator` trait facilitates the implementation of custom mutation logic. It acts on the internal +//! state (context and storage) just before the final forming operation is completed, right before the `FormingEnd` +//! callback is invoked. This trait is crucial for making last-minute adjustments or computations based on the +//! accumulated state in the storage. +//! +//! In this example, the fields `a` and `b` are defined only within the storage and used +//! within the custom mutator to enrich or modify the field `c` of the formed entity. This approach +//! allows for a richer and more flexible formation logic that can adapt based on the intermediate state +//! held within the storage. +//! +//! #### Differences from `FormingEnd` +//! +//! Unlike `FormingEnd`, which is primarily responsible for integrating and finalizing the formation process of a field +//! within a parent former, `form_mutation` directly pertains to the entity itself. This method is designed to be independent +//! of whether the forming process is occurring within the context of a superformer or if the structure is a standalone +//! or nested field. This makes `form_mutation` suitable for entity-specific transformations that should not interfere +//! with the hierarchical forming logic managed by `FormingEnd`. +//! + +#[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] +fn main() {} +#[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] +fn main() +{ + use former::Former; + + #[ derive( Debug, PartialEq, Former ) ] + #[ storage_fields( a : i32, b : Option< String > ) ] + #[ mutator( custom = true ) ] + pub struct Struct1 + { + c : String, + } + + // = former mutator + + impl< Context, Formed > former::FormerMutator + for Struct1FormerDefinitionTypes< Context, Formed > + { + //! Mutates the context and storage of the entity just before the formation process completes. + #[ inline ] + fn form_mutation( storage : &mut Self::Storage, _context : &mut ::core::option::Option< Self::Context > ) + { + storage.a.get_or_insert_with( Default::default ); + storage.b.get_or_insert_with( Default::default ); + storage.c = Some( format!( "{:?} - {}", storage.a.unwrap(), storage.b.as_ref().unwrap() ) ); + } + } + + let got = Struct1::former().a( 13 ).b( "abc" ).c( "def" ).form(); + let exp = Struct1 + { + c : "13 - abc".to_string(), + }; + assert_eq!( got, exp ); + dbg!( got ); + // > got = Struct1 { + // > c: "13 - abc", + // > } + +} diff --git a/module/core/former/examples/former_custom_scalar_setter.rs b/module/core/former/examples/former_custom_scalar_setter.rs new file mode 100644 index 0000000000..e5acb1847b --- /dev/null +++ b/module/core/former/examples/former_custom_scalar_setter.rs @@ -0,0 +1,90 @@ +// Example former_custom_scalar_setter.rs + +//! ## Example : Custom Scalar Setter +//! +//! Use of a scalar setter within a `Former` trait implementation to directly assign a `HashMap` of `Child` entities to a `Parent` structure using a custom setter function. +//! +//! Unlike the more complex subform and container setters shown in previous examples, this example focuses on a straightforward approach to directly set a scalar value within a parent entity. The `Parent` struct manages a `HashMap` of `Child` entities, and the scalar setter is used to set the entire `HashMap` directly. The `child` function within `ParentFormer` is a custom subform setter that plays a crucial role. It uniquely employs the `ChildFormer` to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a container—each child entity in this case. +//! +//! #### Types of Setters +//! +//! It's crucial to understand the differences among subform setters, container setters, and scalar setters: +//! +//! - **Scalar Setter**: Directly sets scalar values or simple fields within the forming entity. Unlike subform or container setters that manage complex objects or collections, scalar setters handle basic data types or individual fields. These are typically straightforward setter methods that do not involve nested formers or additional structuring. +//! +//! - **Container Setter**: Returns a former of the container itself, offering an interface to manage the container as a whole rather than its individual elements. This type of setter is useful for applying configurations or validations to the entire collection, such as a `HashMap` of children. +//! +//! - **Subform Setter**: Returns a former of an element within a container, providing an interface to individually form each element. For example, the `child` method acts as a subform setter, allowing for the addition and configuration of individual `Child` entities within the `Parent`'s `HashMap`. +//! +//! Each type of setter is designed to address different needs in the formation process, ensuring that users can build complex, nested structures or simply set individual field values as required. +//! + +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} + +// Ensure the example only compiles when the appropriate features are enabled. +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + use collection_tools::HashMap; + use former::Former; + + // Child struct with Former derived for builder pattern support + #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Child + { + name : String, + description : String, + } + + // Parent struct to hold children + #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Parent + { + // Use `hint = true` to gennerate sketch of setter. + #[ scalar( setter = false, hint = false ) ] + children : HashMap< String, Child >, + } + + impl< Definition > ParentFormer< Definition > + where + Definition : former::FormerDefinition< Storage = ParentFormerStorage >, + { + #[ inline ] + pub fn children< Src >( mut self, src : Src ) -> Self + where + Src : ::core::convert::Into< HashMap< String, Child > >, + { + debug_assert!( self.storage.children.is_none() ); + self.storage.children = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + self + } + } + + let echo = Child { name : "echo".to_string(), description : "prints all subjects and properties".to_string() }; + let exit = Child { name : "exit".to_string(), description : "just exit".to_string() }; + let mut children = HashMap::new(); + children.insert( echo.name.clone(), echo ); + children.insert( exit.name.clone(), exit ); + let ca = Parent::former() + .children( children ) + .form(); + + dbg!( &ca ); + // > &ca = Parent { + // > child: { + // > "echo": Child { + // > name: "echo", + // > description: "prints all subjects and properties", + // > }, + // > "exit": Child { + // > name: "exit", + // > description: "just exit", + // > }, + // > }, + // > } +} diff --git a/module/core/former/examples/former_custom_setter_overriden.rs b/module/core/former/examples/former_custom_setter_overriden.rs index c817ab6872..4723ab16e2 100644 --- a/module/core/former/examples/former_custom_setter_overriden.rs +++ b/module/core/former/examples/former_custom_setter_overriden.rs @@ -15,7 +15,7 @@ fn main() #[ derive( Debug, Former ) ] pub struct StructWithCustomSetters { - #[ setter( false ) ] + #[ scalar( setter = false ) ] word : String, } diff --git a/module/core/former/examples/former_custom_subform_setter.rs b/module/core/former/examples/former_custom_subform_setter.rs new file mode 100644 index 0000000000..bff35ac6ea --- /dev/null +++ b/module/core/former/examples/former_custom_subform_setter.rs @@ -0,0 +1,103 @@ +// Example former_custom_subformer.rs + +//! +//! This example illustrates the implementation of nested builder patterns using the `Former` trait, emphasizing a parent-child relationship. Here, the `Parent` struct utilizes `ChildFormer` as a custom subformer to dynamically manage its `child` field—a `HashMap`. Each child in the `HashMap` is uniquely identified and configured via the `ChildFormer`. +//! +//! The `child` function within `ParentFormer` is a custom subform setter that plays a crucial role. It uniquely employs the `ChildFormer` to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a container—each child entity in this case. +//! +//! #### Types of Setters +//! +//! It's crucial to understand the differences among subform setters, container setters, and scalar setters: +//! +//! - **Scalar Setter**: Directly sets scalar values or simple fields within the forming entity. Unlike subform or container setters that manage complex objects or collections, scalar setters handle basic data types or individual fields. These are typically straightforward setter methods that do not involve nested formers or additional structuring. +//! +//! - **Container Setter**: Returns a former of the container itself, offering an interface to manage the container as a whole rather than its individual elements. This type of setter is useful for applying configurations or validations to the entire collection, such as a `HashMap` of children. +//! +//! - **Subform Setter**: Returns a former of an element within a container, providing an interface to individually form each element. For example, the `child` method acts as a subform setter, allowing for the addition and configuration of individual `Child` entities within the `Parent`'s `HashMap`. +//! +//! Each type of setter is designed to address different needs in the formation process, ensuring that users can build complex, nested structures or simply set individual field values as required. +//! + +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} + +// Ensure the example only compiles when the appropriate features are enabled. +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + use collection_tools::HashMap; + use former::Former; + + // Child struct with Former derived for builder pattern support + #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Child + { + name : String, + description : String, + } + + // Parent struct to hold children + #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Parent + { + // Use `hint = true` to gennerate sketch of setter. + #[ subform( setter = false, hint = false ) ] + child : HashMap< String, Child >, + } + + /// Initializes and configures a subformer for adding named child entities. This method leverages an internal function + /// to create and return a configured subformer instance. It allows for the dynamic addition of children with specific names, + /// integrating them into the formation process of the parent entity. + /// + impl< Definition > ParentFormer< Definition > + where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, + { + + #[ inline( always ) ] + pub fn child( self, name : &str ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._child_add::< ChildFormer< _ >, _, >() + .name( name ) + } + + } + + // Required to define how `value` is converted into pair `( key, value )` + impl former::ValToEntry< HashMap< String, Child > > for Child + { + type Entry = ( String, Child ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.name.clone(), self ) + } + } + + let ca = Parent::former() + .child( "echo" ) + .description( "prints all subjects and properties" ) // sets additional properties using custom subformer + .end() + .child( "exit" ) + .description( "just exit" ) // Sets additional properties using using custom subformer + .end() + .form(); + + dbg!( &ca ); + // > &ca = Parent { + // > child: { + // > "echo": Child { + // > name: "echo", + // > description: "prints all subjects and properties", + // > }, + // > "exit": Child { + // > name: "exit", + // > description: "just exit", + // > }, + // > }, + // > } +} diff --git a/module/core/former/examples/former_custom_subform_setter2.rs b/module/core/former/examples/former_custom_subform_setter2.rs new file mode 100644 index 0000000000..7c781d6128 --- /dev/null +++ b/module/core/former/examples/former_custom_subform_setter2.rs @@ -0,0 +1,160 @@ +// Example former_custom_subformer2.rs + +//! +//! This example extends the demonstration of nested builder patterns using the `Former` trait, highlighting a parent-child relationship similar to the `former_custom_subformer.rs`. However, this variant, `former_custom_subformer2.rs`, showcases a more flexible but complex approach to managing the `child` field in the `Parent` struct—a `HashMap` of `Child` entities. Instead of relying on a predefined subformer setter (`_child_add`), this example constructs the subformer logic directly using closures. This method provides greater control over how children are added and managed within the `Parent`. +//! +//! #### Custom Subform Setter +//! +//! The `child` function within `ParentFormer` is a custom subform setter that plays a crucial role. It uniquely employs the `ChildFormer` to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a container—each child entity in this case. +//! +//! #### Types of Setters +//! +//! It's crucial to understand the differences among subform setters, container setters, and scalar setters: +//! +//! - **Scalar Setter**: Directly sets scalar values or simple fields within the forming entity. Unlike subform or container setters that manage complex objects or collections, scalar setters handle basic data types or individual fields. These are typically straightforward setter methods that do not involve nested formers or additional structuring. +//! +//! - **Container Setter**: Returns a former of the container itself, offering an interface to manage the container as a whole rather than its individual elements. This type of setter is useful for applying configurations or validations to the entire collection, such as a `HashMap` of children. +//! +//! - **Subform Setter**: Returns a former of an element within a container, providing an interface to individually form each element. For example, the `child` method acts as a subform setter, allowing for the addition and configuration of individual `Child` entities within the `Parent`'s `HashMap`. +//! +//! Each type of setter is designed to address different needs in the formation process, ensuring that users can build complex, nested structures or simply set individual field values as required. +//! + +// Ensure the example only compiles when the appropriate features are enabled. +#[ cfg( not( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ) ] +fn main() {} +#[ cfg( all( feature = "enabled", feature = "derive_former", not( feature = "no_std" ) ) ) ] +fn main() +{ + use collection_tools::HashMap; + use former::Former; + + // Child struct with Former derived for builder pattern support + #[ derive( Clone, Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Child + { + name : String, + description : String, + } + + // Parent struct to hold children + #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. + // #[ debug ] + pub struct Parent + { + // Use `hint = true` to gennerate sketch of setter. + #[ subform( setter = false, hint = false ) ] + child : HashMap< String, Child >, + } + + // Use ChildFormer as custom subformer for ParentFormer to add children by name. + impl< Definition > ParentFormer< Definition > + where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, + { + + /// Adds a named child entity to the `Parent`'s `child` field using a custom subformer setup. + /// This method simplifies the process of dynamically adding child entities with specified names, + /// providing a basic yet powerful example of custom subformer implementation. + /// + #[ inline( always ) ] + pub fn child1( self, name : &str ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + let on_end = | substorage : ChildFormerStorage, super_former : core::option::Option< Self > | -> Self + { + let mut super_former = super_former.unwrap(); + let preformed = former::StoragePreform::preform( substorage ); + + if super_former.storage.child.is_none() + { + super_former.storage.child = Some( Default::default() ); + } + + // add instance to the container + super_former.storage.child.as_mut().unwrap() + .entry( preformed.name.clone() ) + .or_insert( preformed.clone() ); + + super_former + }; + let subformer = ChildAsSubformer::< Self, _ >::begin( None, Some( self ), former::FormingEndClosure::new( on_end ) ); + subformer.name( name ) + } + + /// Dynamically adds named child entities to the `Parent` structure using a custom subformer. + /// Unlike traditional methods that might use predefined setters like `_child_add`, this function + /// explicitly constructs a subformer setup through a closure to provide greater flexibility and control. + /// + #[ inline( always ) ] + pub fn child2( self, name : &str ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + let on_end = | substorage : ChildFormerStorage, super_former : core::option::Option< Self > | -> Self + { + let mut super_former = super_former.unwrap(); + let preformed = former::StoragePreform::preform( substorage ); + + if super_former.storage.child.is_none() + { + super_former.storage.child = Some( Default::default() ); + } + + // add instance to the container + super_former.storage.child.as_mut().unwrap() + .entry( preformed.name.clone() ) + .or_insert( preformed.clone() ); + + // custom logic to add two instances to the container + super_former.storage.child.as_mut().unwrap() + .entry( format!( "{}_2", preformed.name ) ) + .or_insert( preformed.clone() ); + + super_former + }; + let subformer = ChildAsSubformer::< Self, _ >::begin( None, Some( self ), former::FormingEndClosure::new( on_end ) ); + subformer.name( name ) + } + + } + + // Required to define how `value` is converted into pair `( key, value )` + impl former::ValToEntry< HashMap< String, Child > > for Child + { + type Entry = ( String, Child ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.name.clone(), self ) + } + } + + let ca = Parent::former() + .child1( "echo" ) + .description( "prints all subjects and properties" ) // sets additional properties using custom subformer + .end() + .child2( "exit" ) + .description( "just exit" ) // Sets additional properties using using custom subformer + .end() + .form(); + + dbg!( &ca ); + // > &ca = Parent { + // > child: { + // > "echo": Child { + // > name: "echo", + // > description: "prints all subjects and properties", + // > }, + // > "exit": Child { + // > name: "exit", + // > description: "just exit", + // > }, + // > "exit_2": Child { + // > name: "exit", + // > description: "just exit", + // > }, + // > }, + // > } + +} diff --git a/module/core/former/examples/former_custom_subformer.rs b/module/core/former/examples/former_custom_subformer.rs deleted file mode 100644 index 2cf8c2cc7a..0000000000 --- a/module/core/former/examples/former_custom_subformer.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! example of how to use former of another structure as subformer of former of current one -//! function `command` integrate `CommandFormer` into `AggregatorFormer`. - -#[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] -fn main() {} - -#[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] -fn main() -{ - use std::collections::HashMap; - use former::Former; - - // Command struct with Former derived for builder pattern support - #[ derive( Debug, PartialEq, Former ) ] - pub struct Command - { - name : String, - description : String, - } - - // Aggregator struct to hold commands - #[ derive( Debug, PartialEq, Former ) ] - pub struct Aggregator - { - #[ setter( false ) ] - command : HashMap< String, Command >, - } - - // Use CommandFormer as custom subformer for AggregatorFormer to add commands by name. - impl< Context, End > AggregatorFormer< Context, End > - where - End : former::FormingEnd< Aggregator, Context >, - { - #[ inline( always ) ] - pub fn command< IntoName >( self, name : IntoName ) -> CommandFormer< Self, impl former::FormingEnd< Command, Self > > - where - IntoName : core::convert::Into< String >, - { - let on_end = | command : Command, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if let Some( ref mut commands ) = super_former.storage.command - { - commands.insert( command.name.clone(), command ); - } - else - { - let mut commands: HashMap< String, Command > = Default::default(); - commands.insert( command.name.clone(), command ); - super_former.storage.command = Some( commands ); - } - super_former - }; - let former = CommandFormer::begin( None, Some( self ), on_end ); - former.name( name ) - } - // xxx : review - } - - let ca = Aggregator::former() - .command( "echo" ) - .description( "prints all subjects and properties" ) // sets additional properties using custom subformer - .end() - .command( "exit" ) - .description( "just exit" ) // Sets additional properties using using custom subformer - .end() - .form(); - - dbg!( &ca ); - // > &ca = Aggregator { - // > command: { - // > "echo": Command { - // > name: "echo", - // > description: "prints all subjects and properties", - // > }, - // > "exit": Command { - // > name: "exit", - // > description: "just exit", - // > }, - // > }, - // > } -} diff --git a/module/core/former/examples/former_debug.rs b/module/core/former/examples/former_debug.rs index 0a849f684a..8d610eae3c 100644 --- a/module/core/former/examples/former_debug.rs +++ b/module/core/former/examples/former_debug.rs @@ -13,8 +13,8 @@ fn main() #[ derive( Debug, PartialEq, Former ) ] + // Use `#[ debug ]` to expand and debug generate code. // #[ debug ] - // Uncomment to see what derive expand into pub struct UserProfile { age : i32, diff --git a/module/core/former/examples/former_many_fields.rs b/module/core/former/examples/former_many_fields.rs index 6a193b1975..2e04953c77 100644 --- a/module/core/former/examples/former_many_fields.rs +++ b/module/core/former/examples/former_many_fields.rs @@ -7,7 +7,7 @@ //! - `int_1`: A required integer field. //! - `string_1`: A required string field. //! - `vec_1`: A vector of unsigned integers, showcasing collection handling. -//! - `hashmap_strings_1`: A hash map storing key-value pairs, both strings, illustrating how `Former` can manage more complex data structures. +//! - `hashmap_1`: A hash map storing key-value pairs, both strings, illustrating how `Former` can manage more complex data structures. //! - `int_optional_1`: An optional integer field, demonstrating `Former`'s capability to handle optional fields seamlessly. //! - `string_optional_1`: An optional string field, further exemplifying optional field handling. //! @@ -15,7 +15,7 @@ //! //! The builder pattern methods significantly streamline the process of struct initialization, especially for structs with complex or optional fields. By leveraging `Former`, developers can write more readable and maintainable initialization code, avoiding the verbosity and complexity often associated with manual struct instantiation. //! -//! The `dbg!` macro is utilized to print the constructed `Structure1` instance, confirming that all fields are correctly assigned, including the handling of optional fields and collections. This example underscores the power and convenience of using `Former` for struct initialization in Rust projects. +//! The `dbg!` macro is utilized to print the constructed `Structure1` instance, confirming that all fields are correctly assigned, including the handling of optional fields and collections. #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] fn main() {} @@ -31,11 +31,11 @@ fn main() int_1 : i32, string_1 : String, vec_1 : Vec< u32 >, - hashmap_strings_1 : std::collections::HashMap< String, String >, + hashmap_1 : collection_tools::HashMap< String, String >, int_optional_1 : core::option::Option< i32 >, string_optional_1 : Option< String >, } - let hashmap = std::collections::HashMap::from + let hashmap = collection_tools::HashMap::from ([ ( "k1".to_string(), "v1".to_string() ), ( "k2".to_string(), "v2".to_string() ), @@ -45,7 +45,7 @@ fn main() .int_1( 13 ) .string_1( "Abcd".to_string() ) .vec_1( vec![ 1, 3 ] ) - .hashmap_strings_1( hashmap ) + .hashmap_1( hashmap ) .string_optional_1( "dir1" ) .form(); dbg!( &struct1 ); @@ -57,7 +57,7 @@ fn main() // < 1, // < 3, // < ], -// < hashmap_strings_1: { +// < hashmap_1: { // < "k1": "v1", // < "k2": "v2", // < }, diff --git a/module/core/former/examples/former_subformer_hashmap.rs b/module/core/former/examples/former_subformer_hashmap.rs deleted file mode 100644 index 0cfb6dff30..0000000000 --- a/module/core/former/examples/former_subformer_hashmap.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! # Example Usage -//! -//! Demonstrates how to use `HashMapSubformer` with the `HashMapLike` trait to build a `std::collections::HashMap`: -//! - -#[ cfg( not( all( feature = "derive_former", not( feature = "no_std" ) ) ) ) ] -fn main() {} - -#[ cfg( all( feature = "derive_former", not( feature = "no_std" ) ) ) ] -fn main() -{ - use test_tools::exposed::*; - - #[ derive( Debug, PartialEq, former::Former ) ] - pub struct StructWithMap - { - #[ subformer( former::HashMapSubformer ) ] - map : std::collections::HashMap< &'static str, &'static str >, - } - - let struct1 = StructWithMap::former() - .map() - .insert( "a", "b" ) - .insert( "c", "d" ) - .end() - .form() - ; - assert_eq!( struct1, StructWithMap { map : hmap!{ "a" => "b", "c" => "d" } } ); -} diff --git a/module/core/former/examples/former_subformer_hashset.rs b/module/core/former/examples/former_subformer_hashset.rs deleted file mode 100644 index 7ce1d3a365..0000000000 --- a/module/core/former/examples/former_subformer_hashset.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! # Example Usage -//! -//! Demonstrates how to use `HashMapSubformer` with the `HashMapLike` trait to build a `std::collections::HashMap`: -//! - -#[ cfg( not( all( feature = "derive_former", not( feature = "no_std" ) ) ) ) ] -fn main() {} - -#[ cfg( all( feature = "derive_former", not( feature = "no_std" ) ) ) ] -fn main() -{ - use test_tools::exposed::*; - - #[ derive( Debug, PartialEq, former::Former ) ] - pub struct StructWithSet - { - #[ subformer( former::HashSetSubformer ) ] - set : std::collections::HashSet< &'static str >, - } - - let instance = StructWithSet::former() - .set() - .insert("apple") - .insert("banana") - .end() - .form(); - - assert_eq!(instance, StructWithSet { set : hset![ "apple", "banana" ] }); - -} diff --git a/module/core/former/examples/former_subformer_vector.rs b/module/core/former/examples/former_subformer_vector.rs deleted file mode 100644 index 9d7b22bdc0..0000000000 --- a/module/core/former/examples/former_subformer_vector.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! # Example Usage -//! -//! Demonstrates how to use `HashMapSubformer` with the `HashMapLike` trait to build a `std::collections::HashMap`: -//! - -#[ cfg( not( all( feature = "derive_former", not( feature = "no_std" ) ) ) ) ] -fn main() {} - -#[ cfg( all( feature = "derive_former", not( feature = "no_std" ) ) ) ] -fn main() -{ - - #[ derive( Debug, PartialEq, former::Former ) ] - pub struct StructWithVec - { - #[ subformer( former::VectorSubformer ) ] - vec : Vec< &'static str >, - } - - let instance = StructWithVec::former() - .vec() - .push( "apple" ) - .push( "banana" ) - .end() - .form(); - - assert_eq!( instance, StructWithVec { vec: vec![ "apple", "banana" ] } ); - -} diff --git a/module/core/former/examples/former_trivial.rs b/module/core/former/examples/former_trivial.rs index 78331e5577..b330278f68 100644 --- a/module/core/former/examples/former_trivial.rs +++ b/module/core/former/examples/former_trivial.rs @@ -24,7 +24,10 @@ fn main() { use former::Former; + // Use attribute debug to print expanded code. #[ derive( Debug, PartialEq, Former ) ] + // Uncomment to see what derive expand into + // #[ debug ] pub struct UserProfile { age : i32, diff --git a/module/core/former/examples/former_trivial_expaned.rs b/module/core/former/examples/former_trivial_expaned.rs index 5b8d8cd8c2..484f19262b 100644 --- a/module/core/former/examples/former_trivial_expaned.rs +++ b/module/core/former/examples/former_trivial_expaned.rs @@ -1,3 +1,4 @@ +#![ allow( dead_code ) ] //! # Builder Pattern Implementation with Former //! //! This module demonstrates the use of the `Former` trait to apply the builder pattern for Rust structs. @@ -16,17 +17,13 @@ //! This approach abstracts away the need for manually implementing a builder for each struct, making code more readable and maintainable. //! -// xxx : regenerate - -#![ allow( dead_code ) ] - #[ cfg( any( not( feature = "derive_former" ), not( feature = "enabled" ) ) ) ] -fn main(){} - +fn main() {} #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] fn main() { + // Use attribute debug to print expanded code. #[ derive( Debug, PartialEq ) ] pub struct UserProfile { @@ -36,111 +33,204 @@ fn main() } impl UserProfile + where { #[ inline( always ) ] - pub fn former() -> UserProfileFormer< UserProfile, former::ReturnFormed > + pub fn former() -> UserProfileFormer< + UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed > + > { - UserProfileFormer::< UserProfile, former::ReturnFormed >::new() + UserProfileFormer::< UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed > >:: + new_coercing(former::ReturnPreformed) } } - #[ derive( Debug, Default ) ] - pub struct UserProfileFormerStorage + // = entity to + + impl< Definition > former::EntityToFormer< Definition > for UserProfile + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, + { + type Former = UserProfileFormer< Definition >; + } + + impl former::EntityToStorage for UserProfile + where + { + type Storage = UserProfileFormerStorage; + } + + impl< Context, Formed, End > former::EntityToDefinition< Context, Formed, End > for UserProfile< > + where + End : former::FormingEnd< UserProfileFormerDefinitionTypes< Context, Formed > >, + { + type Definition = UserProfileFormerDefinition< Context, Formed, End >; + type Types = UserProfileFormerDefinitionTypes< Context, Formed >; + } + + // = definition + + #[derive(Debug)] + pub struct UserProfileFormerDefinitionTypes< Context = (), Formed = UserProfile, > + where + { + _phantom : core::marker::PhantomData< (*const Context, *const Formed) >, + } + + impl< Context, Formed, > ::core::default::Default for UserProfileFormerDefinitionTypes< Context, Formed, > + where + { + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + } + + impl< Context, Formed, > former::FormerDefinitionTypes for UserProfileFormerDefinitionTypes< Context, Formed, > + where + { + type Storage = UserProfileFormerStorage; + type Formed = Formed; + type Context = Context; + } + + #[derive(Debug)] + pub struct UserProfileFormerDefinition< Context = (), Formed = UserProfile, End = former::ReturnPreformed, > + where + { + _phantom : core::marker::PhantomData< (*const Context, *const Formed, *const End) >, + } + + impl< Context, Formed, End, > ::core::default::Default for UserProfileFormerDefinition< Context, Formed, End, > + where + { + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + } + + impl< Context, Formed, End, > former::FormerDefinition for UserProfileFormerDefinition< Context, Formed, End, > + where + End : former::FormingEnd< UserProfileFormerDefinitionTypes< Context, Formed, > >, { - age : Option< i32 >, - username : Option< String >, - bio_optional : Option< String >, + type Types = UserProfileFormerDefinitionTypes< Context, Formed, >; + type End = End; + type Storage = UserProfileFormerStorage; + type Formed = Formed; + type Context = Context; } - pub struct UserProfileFormer - < - Context = UserProfile, - End = former::ReturnFormed, - > + impl< Context, Formed, > former::FormerMutator for UserProfileFormerDefinitionTypes< Context, Formed, > + where + {} + + // = storage + + pub struct UserProfileFormerStorage where - End : former::FormingEnd< UserProfile, Context >, { - storage : UserProfileFormerStorage, - context : Option< Context >, - on_end : Option< End >, + pub age : ::core::option::Option< i32 >, + pub username : ::core::option::Option< String >, + pub bio_optional : Option< String >, } - impl< Context, End > UserProfileFormer< Context, End > + impl ::core::default::Default for UserProfileFormerStorage where - End : former::FormingEnd< UserProfile, Context >, { #[ inline( always ) ] - pub fn form( mut self ) -> UserProfile + fn default() -> Self + { + Self + { + age : ::core::option::Option::None, + username : ::core::option::Option::None, + bio_optional : ::core::option::Option::None, + } + } + } + + impl former::Storage for UserProfileFormerStorage + where + { + type Preformed = UserProfile; + } + + impl former::StoragePreform for UserProfileFormerStorage + where + { + // type Preformed = UserProfile; + fn preform(mut self) -> Self::Preformed { - let age = if self.storage.age.is_some() + let age = if self.age.is_some() { - self.storage.age.take().unwrap() + self.age.take().unwrap() } else { - let val : i32 = { - trait NotDefault< T > - { - fn maybe_default( self : &Self ) -> T { panic!( "Field 'age' isn't initialized" ) } - } - trait WithDefault< T > + trait MaybeDefault< T > { - fn maybe_default( self : &Self ) -> T; + fn maybe_default(self : &Self) -> T + { + panic!("Field 'age' isn't initialized") + } } - impl< T > NotDefault< T > for &::core::marker::PhantomData< T > {} - impl< T > WithDefault< T > for ::core::marker::PhantomData< T > - where - T : ::core::default::Default, + impl< T > MaybeDefault< T > for &::core::marker::PhantomData< T > + {} + impl< T > MaybeDefault< T > for ::core::marker::PhantomData< T > + where T : ::core::default::Default, { - fn maybe_default( self : &Self ) -> T + fn maybe_default(self : &Self) -> T { T::default() } } - ( &::core::marker::PhantomData::< i32 > ).maybe_default() - }; - val + (&::core::marker::PhantomData::< i32 >).maybe_default() + } }; - let username = if self.storage.username.is_some() + let username = if self.username.is_some() { - self.storage.username.take().unwrap() + self.username.take().unwrap() } else { - let val : String = { - trait NotDefault< T > + trait MaybeDefault< T > { - fn maybe_default( self : &Self ) -> T { panic!( "Field 'username' isn't initialized" ) } - } - trait WithDefault< T > - { - fn maybe_default( self : &Self ) -> T; + fn maybe_default(self : &Self) -> T + { + panic!("Field 'username' isn't initialized") + } } - impl< T > NotDefault< T > for &::core::marker::PhantomData< T > {} - impl< T > WithDefault< T > for ::core::marker::PhantomData< T > - where - T : ::core::default::Default, + impl< T > MaybeDefault< T > for &::core::marker::PhantomData< T > + {} + impl< T > MaybeDefault< T > for ::core::marker::PhantomData< T > + where T : ::core::default::Default, { - fn maybe_default( self : &Self ) -> T + fn maybe_default(self : &Self) -> T { T::default() } } - ( &::core::marker::PhantomData::< String > ).maybe_default() - }; - val + (&::core::marker::PhantomData::< String >).maybe_default() + } }; - let bio_optional = if self.storage.bio_optional.is_some() + let bio_optional = if self.bio_optional.is_some() { - Option::Some( self.storage.bio_optional.take().unwrap() ) + ::core::option::Option::Some(self.bio_optional.take().unwrap()) } else { - Option::None + ::core::option::Option::None }; - let result = UserProfile + let result = UserProfile::<> { age, username, @@ -148,83 +238,167 @@ fn main() }; return result; } + } + + pub struct UserProfileFormer< Definition = UserProfileFormerDefinition< (), UserProfile, former::ReturnPreformed >, > + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, + { + pub storage : Definition::Storage, + pub context : core::option::Option< Definition::Context >, + pub on_end : core::option::Option< Definition::End >, + } + impl< Definition, > UserProfileFormer< Definition, > + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, Definition::Types : former::FormerDefinitionTypes< Storage = UserProfileFormerStorage >, + { #[ inline( always ) ] - pub fn perform( self ) -> UserProfile + pub fn new(on_end : Definition::End) -> Self { - let result = self.form(); - return result; + Self::begin_coercing(None, None, on_end) } #[ inline( always ) ] - pub fn new() -> UserProfileFormer< UserProfile, former::ReturnFormed > + pub fn new_coercing< IntoEnd >(end : IntoEnd) -> Self + where IntoEnd : Into< Definition::End >, { - UserProfileFormer::< UserProfile, former::ReturnFormed >::begin( None, former::ReturnFormed ) + Self::begin_coercing(None, None, end,) } #[ inline( always ) ] - pub fn begin - ( - context : Option< Context >, - on_end : End, - ) -> Self + pub fn begin(mut storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : ::End,) -> Self { + if storage.is_none() + { + storage = Some(::core::default::Default::default()); + } Self { - storage : core::default::Default::default(), + storage : storage.unwrap(), context : context, - on_end : Option::Some( on_end ), + on_end : ::core::option::Option::Some(on_end), + } + } + + #[ inline( always ) ] + pub fn begin_coercing< IntoEnd >(mut storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : IntoEnd,) -> Self + where IntoEnd : ::core::convert::Into< ::End >, + { + if storage.is_none() + { + storage = Some(::core::default::Default::default()); } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some(::core::convert::Into::into(on_end)), + } + } + + #[ inline( always ) ] + pub fn form(self) -> ::Formed + { + self.end() } #[ inline( always ) ] - pub fn end( mut self ) -> Context + pub fn end(mut self) -> ::Formed { let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) + let mut context = self.context.take(); + ::form_mutation(&mut self.storage, &mut context); + former::FormingEnd::::call(&on_end, self.storage, context) } - #[ inline ] - pub fn age< Src >( mut self, src : Src ) -> Self - where - Src : Into< i32 >, + #[ inline( always ) ] + pub fn age< Src >(mut self, src : Src) -> Self + where Src : ::core::convert::Into< i32 >, { - debug_assert!( self.storage.age.is_none() ); - self.storage.age = Option::Some( src.into() ); + debug_assert!(self.storage.age.is_none()); + self.storage.age = ::core::option::Option::Some(::core::convert::Into::into(src)); self } - #[ inline ] - pub fn username< Src >( mut self, src : Src ) -> Self - where - Src : Into< String >, + #[ inline( always ) ] + pub fn username< Src >(mut self, src : Src) -> Self + where Src : ::core::convert::Into< String >, { - debug_assert!( self.storage.username.is_none() ); - self.storage.username = Option::Some( src.into() ); + debug_assert!(self.storage.username.is_none()); + self.storage.username = ::core::option::Option::Some(::core::convert::Into::into(src)); self } - #[ inline ] - pub fn bio_optional< Src >( mut self, src : Src ) -> Self - where - Src : Into< String >, + #[ inline( always ) ] + pub fn bio_optional< Src >(mut self, src : Src) -> Self + where Src : ::core::convert::Into< String >, { - debug_assert!( self.storage.bio_optional.is_none() ); - self.storage.bio_optional = Option::Some( src.into() ); + debug_assert!(self.storage.bio_optional.is_none()); + self.storage.bio_optional = ::core::option::Option::Some(::core::convert::Into::into(src)); self } } + impl< Definition, > UserProfileFormer< Definition, > + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage, Formed = UserProfile >, + { + pub fn preform(self) -> ::Formed + { + former::StoragePreform::preform(self.storage) + } + } + + impl< Definition, > UserProfileFormer< Definition, > + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage, Formed = UserProfile, >, + { + #[ inline( always ) ] + pub fn perform(self) -> Definition::Formed + { + let result = self.form(); + return result; + } + } + + impl< Definition > former::FormerBegin< Definition > for UserProfileFormer< Definition, > + where + Definition : former::FormerDefinition< Storage = UserProfileFormerStorage >, + { + #[ inline( always ) ] + fn former_begin(storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : Definition::End,) -> Self + { + debug_assert!(storage.is_none()); + Self::begin(None, context, on_end) + } + } + + // = as subformer + + pub type UserProfileAsSubformer< Superformer, End > = + UserProfileFormer< UserProfileFormerDefinition< Superformer, Superformer, End, >, >; + + pub trait UserProfileAsSubformerEnd< SuperFormer > + where + Self : former::FormingEnd< UserProfileFormerDefinitionTypes< SuperFormer, SuperFormer >, >, {} + + impl< SuperFormer, T > UserProfileAsSubformerEnd< SuperFormer > for T + where + Self : former::FormingEnd< UserProfileFormerDefinitionTypes< SuperFormer, SuperFormer >, >, + {} + + // = end + let profile = UserProfile::former() .age( 30 ) .username( "JohnDoe".to_string() ) - .bio_optional( "Software Developer".to_string() ) + .bio_optional( "Software Developer".to_string() ) // Optionally provide a bio .form(); - dbg!( &profile ); + // Expected output: + // // &profile = UserProfile { // age: 30, // username: "JohnDoe", diff --git a/module/core/former/src/axiomatic.rs b/module/core/former/src/axiomatic.rs index 161a0f3ea8..e69de29bb2 100644 --- a/module/core/former/src/axiomatic.rs +++ b/module/core/former/src/axiomatic.rs @@ -1,192 +0,0 @@ -//! .... - -/// Defines a handler for the end of a subforming process, enabling the return of the original context. -/// -/// This trait is designed to be flexible, allowing for various end-of-forming behaviors in builder patterns. -/// Implementors can define how to transform or pass through the context during the forming process's completion. -/// -/// # Parameters -/// - `Formed`: The type of the container being processed. -/// - `Context`: The type of the context that might be altered or returned upon completion. -pub trait FormingEnd< Formed, Context > -{ - /// Called at the end of the subforming process to return the modified or original context. - /// - /// # Parameters - /// - `container`: The container being processed. - /// - `context`: Optional context to be transformed or returned. - /// - /// # Returns - /// Returns the transformed or original context based on the implementation. - #[ allow( dead_code ) ] - fn call( &self, storage : Formed, context : core::option::Option< Context > ) -> Context; -} - -impl< Storage, Context, F > FormingEnd< Storage, Context > for F -where - F : Fn( Storage, core::option::Option< Context > ) -> Context, -{ - #[ inline( always ) ] - fn call( &self, storage : Storage, context : core::option::Option< Context > ) -> Context - { - self( storage, context ) - } -} - -/// A wrapper around a closure to be used as a `FormingEnd`. -/// -/// This struct allows for dynamic dispatch of a closure that matches the -/// `FormingEnd` trait's `call` method signature. It is useful for cases where -/// a closure needs to be stored or passed around as an object implementing -/// `FormingEnd`. -/// -/// # Type Parameters -/// -/// * `Storage` - The type of the container being processed. This type is passed to the closure -/// when it's called. -/// * `Context` - The type of the context that may be altered or returned by the closure. -/// This allows for flexible manipulation of context based on the container. -#[ cfg( not( feature = "no_std" ) ) ] -pub struct FormingEndWrapper< Storage, Context > -{ - closure : Box< dyn Fn( Storage, Option< Context > ) -> Context >, - _marker : std::marker::PhantomData< Storage >, -} - -#[ cfg( not( feature = "no_std" ) ) ] -impl< Storage, Context > FormingEndWrapper< Storage, Context > -{ - /// Constructs a new `FormingEndWrapper` with the provided closure. - /// - /// # Parameters - /// - /// * `closure` - A closure that matches the expected signature for transforming a container - /// and context into a new context. This closure is stored and called by the - /// `call` method of the `FormingEnd` trait implementation. - /// - /// # Returns - /// - /// Returns an instance of `FormingEndWrapper` encapsulating the provided closure. - pub fn new( closure : impl Fn( Storage, Option< Context > ) -> Context + 'static ) -> Self - { - Self - { - closure : Box::new( closure ), - _marker : std::marker::PhantomData - } - } -} - -#[ cfg( not( feature = "no_std" ) ) ] -use std::fmt; -#[ cfg( not( feature = "no_std" ) ) ] -impl< Storage, Context > fmt::Debug for FormingEndWrapper< Storage, Context > -{ - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result - { - f.debug_struct( "FormingEndWrapper" ) - .field( "closure", &format_args!{ "- closure -" } ) - .field( "_marker", &self._marker ) - .finish() - } -} - -#[ cfg( not( feature = "no_std" ) ) ] -impl< Storage, Context > FormingEnd< Storage, Context > -for FormingEndWrapper< Storage, Context > -{ - fn call( &self, storage : Storage, context : Option< Context > ) -> Context - { - ( self.closure )( storage, context ) - } -} - -// /// A `FormingEnd` implementation that returns the original context without any modifications. -// /// -// /// This struct is used when no end-of-forming processing is needed, and the original context is to be returned as-is. -// #[ derive( Debug, Default ) ] -// pub struct NoEnd; -// -// impl< Formed, Context > FormingEnd< Formed, Context > -// for NoEnd -// { -// #[ inline( always ) ] -// fn call( &self, _formed : Formed, context : core::option::Option< Context > ) -> Context -// { -// context.unwrap() -// } -// } - -/// A `FormingEnd` implementation that returns the formed container itself instead of the context. -/// -/// This struct is useful when the forming process should result in the formed container being returned directly, -/// bypassing any additional context processing. It simplifies scenarios where the formed container is the final result. -#[ derive( Debug, Default ) ] -pub struct ReturnFormed; - -impl< Storage > FormingEnd< Storage, Storage > -for ReturnFormed -{ - #[ inline( always ) ] - fn call( &self, storage : Storage, _context : core::option::Option< Storage > ) -> Storage - { - storage - } -} - -// - -/// A trait defining the initialization process for a subformer with contextual linkage. -/// -/// This trait is designed for types that need to initiate a subforming process, -/// passing themselves as the context and specifying a closure or handler (`on_end`) to be -/// called upon completion. It facilitates the construction of builder pattern chains -/// that maintain stateful context through each step of the process. -/// -/// # Type Parameters -/// -/// * `Formed` - Represents the type that is being constructed or transformed by the subformer. -/// * `Context` - Denotes the contextual information or the environment in which `Formed` is being formed. -/// This could be a reference to a parent builder, configuration settings, or any other -/// relevant state. -/// -/// # Associated Types -/// -/// * `End` - Specifies the trait bound for the closure or handler that gets called at the completion -/// of the subforming process. This type must implement the `FormingEnd` -/// trait, which defines how the final transformation or construction of `Formed` is handled, -/// potentially using the provided `Context`. -/// - -pub trait FormerBegin< Storage, Formed, Context > -{ - - /// * `End` - Specifies the trait bound for the closure or handler that gets called at the completion - /// of the subforming process. This type must implement the `FormingEnd` - /// trait, which defines how the final transformation or construction of `Formed` is handled, - /// potentially using the provided `Context`. - type End : FormingEnd< Formed, Context >; - - /// Initializes the subforming process by setting the context and specifying an `on_end` completion handler. - /// - /// This function is the entry point for initiating a subforming sequence, allowing the caller - /// to establish initial contextual information and define how the process concludes. - /// - /// # Parameters - /// - /// * `context` - An optional parameter providing initial context for the subforming process. This - /// might include configuration data, references to parent structures, or any state - /// relevant to the formation of `Formed`. - /// - /// * `on_end` - A closure or handler of type `Self::End` that is invoked at the completion of - /// the subforming process. This handler is responsible for applying any final transformations - /// to `Formed` and potentially utilizing `Context` to influence the outcome. - /// - fn _begin - ( - storage : core::option::Option< Storage >, - context : core::option::Option< Context >, - on_end : Self::End, - ) -> Self; - -} diff --git a/module/core/former/src/axiomatic2.rs b/module/core/former/src/axiomatic2.rs deleted file mode 100644 index 16f43060ef..0000000000 --- a/module/core/former/src/axiomatic2.rs +++ /dev/null @@ -1,207 +0,0 @@ -//! .... - -/// xxx2 -pub trait StoragePerform -{ - type Formed; - fn preform( self ) -> Self::Formed; -} - -/// xxx2 -pub trait FormerDescriptor -{ - type Storage : StoragePerform< Formed = Self::Formed >; - type Formed; - // type Former; -} - -pub trait FormerDefinition -{ - type Storage : StoragePerform< Formed = Self::Formed >; - type Formed; - type Context; - type FormerDescriptor : FormerDescriptor< Storage = Self::Storage, Formed = Self::Formed >; - type End : FormingEnd2< Self::FormerDescriptor, Self::Context >; -} - -/// Defines a handler for the end of a subforming process, enabling the return of the original context. -/// -/// This trait is designed to be flexible, allowing for various end-of-forming behaviors in builder patterns. -/// Implementors can define how to transform or pass through the context during the forming process's completion. -/// -/// # Parameters -/// - `Storage`: The type of the container being processed. -/// - `Context`: The type of the context that might be altered or returned upon completion. -pub trait FormingEnd2< Former : FormerDescriptor, Context > -{ - /// Called at the end of the subforming process to return the modified or original context. - /// - /// # Parameters - /// - `container`: The container being processed. - /// - `context`: Optional context to be transformed or returned. - /// - /// # Returns - /// Returns the transformed or original context based on the implementation. - // #[ allow( dead_code ) ] - fn call( &self, storage : Former::Storage, context : core::option::Option< Context > ) -> Former::Formed; -} - -impl< Former : FormerDescriptor, Context, F > FormingEnd2< Former, Context > for F -where - F : Fn( Former::Storage, core::option::Option< Context > ) -> Former::Formed, -{ - #[ inline( always ) ] - fn call( &self, storage : Former::Storage, context : core::option::Option< Context > ) -> Former::Formed - { - self( storage, context ) - } -} - -/// A `FormingEnd2` implementation that returns the formed container itself instead of the context. -/// -/// This struct is useful when the forming process should result in the formed container being returned directly, -/// bypassing any additional context processing. It simplifies scenarios where the formed container is the final result. -#[ derive( Debug, Default ) ] -pub struct ReturnStorage2; - -impl< Former : FormerDescriptor > FormingEnd2< Former, () > -for ReturnStorage2 -// where - // Storage : StoragePreform<>, -{ - #[ inline( always ) ] - fn call( &self, storage : Former::Storage, _context : core::option::Option< () > ) -> Former::Formed - { - storage.preform() - } -} - -/// A wrapper around a closure to be used as a `FormingEnd2`. -/// -/// This struct allows for dynamic dispatch of a closure that matches the -/// `FormingEnd2` trait's `call` method signature. It is useful for cases where -/// a closure needs to be stored or passed around as an object implementing -/// `FormingEnd2`. -/// -/// # Type Parameters -/// -/// * `Storage` - The type of the container being processed. This type is passed to the closure -/// when it's called. -/// * `Context` - The type of the context that may be altered or returned by the closure. -/// This allows for flexible manipulation of context based on the container. -#[ cfg( not( feature = "no_std" ) ) ] -pub struct FormingEndWrapper2< Former : FormerDescriptor, Context > -{ - closure : Box< dyn Fn( Former::Storage, Option< Context > ) -> Former::Formed >, - _marker : std::marker::PhantomData< Former::Storage >, -} - -#[ cfg( not( feature = "no_std" ) ) ] -impl< Former : FormerDescriptor, Context > FormingEndWrapper2< Former, Context > -{ - /// Constructs a new `FormingEndWrapper2` with the provided closure. - /// - /// # Parameters - /// - /// * `closure` - A closure that matches the expected signature for transforming a container - /// and context into a new context. This closure is stored and called by the - /// `call` method of the `FormingEnd2` trait implementation. - /// - /// # Returns - /// - /// Returns an instance of `FormingEndWrapper2` encapsulating the provided closure. - pub fn new( closure : impl Fn( Former::Storage, Option< Context > ) -> Former::Formed + 'static ) -> Self - { - Self - { - closure : Box::new( closure ), - _marker : std::marker::PhantomData - } - } -} - -#[ cfg( not( feature = "no_std" ) ) ] -use std::fmt; -#[ cfg( not( feature = "no_std" ) ) ] -impl< Former : FormerDescriptor, Context > fmt::Debug for FormingEndWrapper2< Former, Context > -{ - fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result - { - f.debug_struct( "FormingEndWrapper2" ) - .field( "closure", &format_args!{ "- closure -" } ) - .field( "_marker", &self._marker ) - .finish() - } -} - -#[ cfg( not( feature = "no_std" ) ) ] -impl< Former : FormerDescriptor, Context > FormingEnd2< Former, Context > -for FormingEndWrapper2< Former, Context > -{ - fn call( &self, storage : Former::Storage, context : Option< Context > ) -> Former::Formed - { - ( self.closure )( storage, context ) - } -} - -// - -/// A trait for initiating a structured subforming process with contextual and intermediary storage linkage. -/// -/// This trait facilitates the creation of a subformer that carries through a builder pattern chain, -/// utilizing intermediary storage for accumulating state or data before finally transforming it into -/// a `Formed` structure. It is designed for scenarios where a multi-step construction or transformation -/// process benefits from maintaining both transient state (`Storage`) and contextual information (`Context`), -/// before concluding with the generation of a final product (`Formed`). -/// -/// # Type Parameters -/// -/// * `Storage` - Represents a mutable intermediary storage structure used throughout the subforming process -/// to accumulate data, state, or partial computations. This storage is internal to the -/// subformer and is eventually converted into the final `Formed` structure by the subformer, -/// not directly by implementations of this trait. -/// -/// * `Formed` - Denotes the final type that results from the subforming process. This is the intended outcome -/// of the builder chain, constructed or transformed from the `Storage` with consideration of -/// the provided `Context`. -/// -/// * `Context` - Specifies the contextual backdrop against which the subforming process unfolds. This could -/// encompass references to parent builders, configuration data, or any state influencing how -/// `Storage` transitions into `Formed`. -/// -/// # Functions -/// -/// * `_begin` - This function launches the subforming process, marking the start of a construction or transformation -/// sequence defined by the implementing type. It establishes the foundational `Storage` and `Context`, -/// alongside specifying an `on_end` completion handler that dictates the final conversion into `Formed`. -/// -/// The `FormerBegin2` trait, by decoupling `Storage` from `Formed` and introducing a contextual layer, enables -/// sophisticated and flexible construction patterns conducive to complex data transformations or object creation -/// sequences within builder patterns. - -// xxx2 : change sequence -pub trait FormerBegin2< Former : FormerDescriptor, Context > -{ - - /// * `End` - A trait bound marking the closure or handler invoked upon completing the subforming process. Implementers - /// of this trait (`End`) are tasked with applying the final transformations that transition `Storage` - /// into `Formed`, optionally utilizing `Context` to guide this transformation. It is crucial that this - /// associated type satisfies the `FormingEnd2` trait, defining the precise mechanics of - /// how the subformer concludes its operation. - type End : FormingEnd2< Former, Context >; - - /// Launches the subforming process with an initial storage and context, setting up an `on_end` completion handler. - /// - /// # Parameters - /// - /// * `storage` - An optional initial state for the intermediary storage structure. - /// * `context` - An optional initial setting providing contextual information for the subforming process. - /// * `on_end` - A completion handler responsible for transforming the accumulated `Storage` into the final `Formed` structure. - fn _begin - ( - storage : core::option::Option< Former::Storage >, - context : core::option::Option< Context >, - on_end : Self::End, - ) -> Self; - -} diff --git a/module/core/former/src/container.rs b/module/core/former/src/container.rs index cfdbfdb4d1..2e0897da73 100644 --- a/module/core/former/src/container.rs +++ b/module/core/former/src/container.rs @@ -1,190 +1,564 @@ -//! Interface for containers. - -/// A trait defining the capability to add elements to a container. -/// -/// This trait should be implemented by container types that require a generic interface -/// for adding new elements. It abstracts over the specific details of how elements are -/// added to the container, providing a consistent API regardless of the underlying -/// container's structure. -/// -/// # Type Parameters -/// -/// - There are no explicit type parameters for the trait itself, but implementers will -/// specify their own types as needed. -/// -/// # Associated Types -/// -/// * `Element`: The type of elements that can be added to the container. This type is -/// defined by the implementer of the trait, allowing for flexibility in the kinds of -/// elements different containers can accept. -/// -pub trait ContainerAdd +//! +//! This module defines traits and structures that facilitate the management and manipulation +//! of container data structures within a builder pattern context. It provides a comprehensive +//! interface for adding, managing, and converting elements within various types of containers, +//! such as vectors, hash maps, and custom container implementations. +//! + +/// Internal namespace. +pub( crate ) mod private { - /// The type of elements to be added to the container. - type Element; - /// Adds an element to the container. - /// - /// Implementations of this function should add the provided element to the container, - /// respecting the container's specific semantics for element addition (e.g., handling - /// duplicates or maintaining order). The function returns a boolean indicating whether - /// the addition was successful. - /// - /// # Parameters - /// - /// * `e`: The element to be added to the container. The type of the element is specified - /// by the associated `Element` type. - /// - /// # Returns - /// - /// Returns `true` if the element was successfully added to the container, or `false` if - /// the addition failed. Failure conditions are defined by the implementer but may include - /// situations like the container being at capacity or the element already existing in a - /// set. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use former::ContainerAdd; + use crate::*; + + /// Facilitates the conversion of container entries to their corresponding value representations. /// - /// struct MyContainer - /// { - /// elements : Vec< i32 >, - /// } + /// This trait is utilized to transform an entry of a container into a value, abstracting the operation of containers + /// like vectors or hash maps. It ensures that even in complex container structures, entries can be seamlessly managed + /// and manipulated as values. + pub trait EntryToVal< Container > + { + /// The type of values stored in the container. This might be distinct from `Entry` in complex containers. + /// For example, in a `HashMap`, while `Entry` might be a ( key, value ) tuple, `Val` might only be the value part. + type Val; + + /// Converts an entry into a value representation specific to the type of container. This conversion is crucial + /// for handling operations on entries, especially when they need to be treated or accessed as individual values, + /// such as retrieving the value part from a key-value pair in a hash map. + fn entry_to_val( self ) -> Self::Val; + } + + impl< C, E > EntryToVal< C > for E + where + C : Container< Entry = E >, + { + type Val = C::Val; + + fn entry_to_val( self ) -> Self::Val + { + C::entry_to_val( self ) + } + } + + /// Provides a mechanism for transforming a value back into a container-specific entry format. /// - /// impl ContainerAdd for MyContainer - /// { - /// type Element = i32; + /// This trait is particularly valuable in scenarios where the operations on a container require + /// not just the manipulation of values but also the re-integration of these values as entries. + /// It is especially crucial in complex data structures, such as `HashMap`s, where entries + /// often involve a key-value pair, and simple values need to be restructured to fit this model + /// for operations like insertion or update. + + pub trait ContainerValToEntry< Val > + { + /// The specific type of entry that corresponds to the value within the container. + /// For example, in a `HashMap`, this might be a tuple of a key and a value. + type Entry; + + /// Converts a value into a container-specific entry, facilitating operations that modify + /// the container. This method is key for ensuring that values can be correctly integrated + /// back into the container, particularly when the entry type is more complex than the value. + /// + /// # Parameters + /// * `val` - The value to be converted into an entry. + /// + /// # Returns + /// Returns the entry constructed from the provided value, ready for insertion or other modifications. + /// + /// # Example + /// ``` + /// use former::ContainerValToEntry; + /// + /// struct PairMap; + /// + /// impl ContainerValToEntry< ( i32, i32 ) > for PairMap + /// { + /// type Entry = ( String, i32 ); + /// + /// fn val_to_entry( val : ( i32, i32 ) ) -> Self::Entry + /// { + /// (val.0.to_string(), val.1) + /// } + /// } + /// ``` + fn val_to_entry( val : Val ) -> Self::Entry; + } + + /// Facilitates the conversion of values back into entries for specific container types. /// - /// fn add( &mut self, e : Self::Element ) -> bool - /// { - /// if self.elements.contains( &e ) - /// { - /// false - /// } - /// else - /// { - /// self.elements.push( e ); - /// true - /// } - /// } - /// } + /// This trait wraps the functionality of `ContainerValToEntry`, providing a more ergonomic + /// interface for converting values directly within the type they pertain to. It is useful + /// in maintaining the integrity of container operations, especially when dealing with + /// sophisticated structures that separate the concept of values and entries, such as `HashMap`s + /// and other associative containers. + pub trait ValToEntry< Container > + { + /// Represents the type of entry that corresponds to the value within the container. + type Entry; + + /// Transforms the instance (value) into an entry compatible with the specified container. + /// This conversion is essential for operations like insertion or modification within the container, + /// where the value needs to be formatted as an entry. + /// + /// # Returns + /// Returns the entry constructed from the instance of the value, ready for integration into the container. + /// + /// # Example + /// ``` + /// use former::ValToEntry; + /// + /// struct PairMap; + /// + /// impl ValToEntry< PairMap > for (i32, i32) + /// { + /// type Entry = ( String, i32 ); + /// + /// fn val_to_entry( self ) -> Self::Entry + /// { + /// (self.0.to_string(), self.1) + /// } + /// } + /// ``` + fn val_to_entry( self ) -> Self::Entry; + } + + impl< C, Val > ValToEntry< C > for Val + where + C : ContainerValToEntry< Val >, + { + type Entry = C::Entry; + + /// Invokes the `val_to_entry` function of the `ContainerValToEntry` trait to convert the value to an entry. + fn val_to_entry( self ) -> C::Entry + { + C::val_to_entry( self ) + } + } + + /// Represents a container by defining the types of entries and values it handles. /// - /// let mut container = MyContainer { elements : vec![] }; - /// assert!( container.add( 10 ) ); // Returns true, element added - /// assert!( !container.add( 10 ) ); // Returns false, element already exists - /// ``` + /// This trait abstracts the nature of containers in data structures, facilitating the handling of contained + /// entries and values, especially in scenarios where the structure of the container allows for complex relationships, + /// such as `HashMap`s. It not only identifies what constitutes an entry and a value in the context of the container + /// but also provides utility for converting between these two, which is critical in operations involving entry manipulation + /// and value retrieval. + + pub trait Container + { + /// The type of entries that can be added to the container. This type can differ from `Val` in containers like `HashMap`, + /// where an entry might represent a key-value pair, and `Val` could represent just the value or the key. + type Entry; + + /// The type of values stored in the container. This might be distinct from `Entry` in complex containers. + /// For example, in a `HashMap`, while `Entry` might be a ( key, value ) tuple, `Val` might only be the value part. + type Val; + + /// Converts an entry to its corresponding value within the container. This function is essential for abstracting + /// the container's internal representation from the values it manipulates. + fn entry_to_val( e : Self::Entry ) -> Self::Val; + } + + /// Provides functionality to add individual entries to a container. /// - /// This example demonstrates a simple container that does not allow duplicate elements. - /// The `add` method checks for the existence of the element before adding it, returning - /// `false` if the element is already present. + /// This trait extends the basic `Container` trait by introducing a method to add entries to a container. + /// It is designed to handle the container's specific requirements and rules for adding entries, such as + /// managing duplicates, maintaining order, or handling capacity constraints. + pub trait ContainerAdd : Container + { + /// Adds an entry to the container and returns a boolean indicating the success of the operation. + /// + /// Implementations should ensure that the entry is added according to the rules of the container, + /// which might involve checking for duplicates, ordering, or capacity limits. + /// + /// # Parameters + /// + /// * `e`: The entry to be added to the container, where the type `Entry` is defined by the `Container` trait. + /// + /// # Returns + /// + /// Returns `true` if the entry was successfully added, or `false` if not added due to reasons such as + /// the entry already existing in the container or the container reaching its capacity. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```rust + /// + /// use former::{ Container, ContainerAdd }; + /// + /// struct MyContainer + /// { + /// entries : Vec< i32 >, + /// } + /// + /// impl Container for MyContainer + /// { + /// type Entry = i32; + /// type Val = i32; + /// + /// #[ inline( always ) ] + /// fn entry_to_val( e : Self::Entry ) -> Self::Val + /// { + /// e + /// } + /// + /// } + /// + /// impl ContainerAdd for MyContainer + /// { + /// fn add( &mut self, e : Self::Entry ) -> bool + /// { + /// if self.entries.contains( &e ) + /// { + /// false + /// } + /// else + /// { + /// self.entries.push( e ); + /// true + /// } + /// } + /// } + /// + /// let mut container = MyContainer { entries : vec![] }; + /// assert!( container.add( 10 ) ); // Returns true, entry added + /// assert!( !container.add( 10 ) ); // Returns false, entry already exists + /// ``` + fn add( &mut self, e : Self::Entry ) -> bool; + } + + /// Defines the capability to replace all entries in a container with a new set of entries. /// - fn add( &mut self, e : Self::Element ) -> bool; + /// This trait extends the `Container` trait by providing a method to replace the existing entries in + /// the container with a new set. This can be useful for resetting the container's contents or bulk-updating + /// them based on external criteria or operations. + pub trait ContainerAssign : Container + where + Self : IntoIterator< Item = Self::Entry >, + { + /// Replaces all entries in the container with the provided entries and returns the count of new entries added. + /// + /// This method clears the existing entries and populates the container with new ones provided by an iterator. + /// It is ideal for scenarios where the container needs to be refreshed or updated with a new batch of entries. + /// + /// # Parameters + /// + /// * `entries` : An iterator over the entries to be added to the container. The entries must conform to + /// the `Entry` type defined by the `Container` trait. + /// + /// # Returns + /// + /// Returns the number of entries successfully added to the container. This count may differ from the total + /// number of entries in the iterator if the container imposes restrictions such as capacity limits or duplicate + /// handling. + /// + /// # Examples + /// + /// ```rust + /// use former::{ Container, ContainerAssign }; + /// + /// struct MyContainer + /// { + /// entries : Vec< i32 >, + /// } + /// + /// impl Container for MyContainer + /// { + /// type Entry = i32; + /// type Val = i32; + /// + /// #[ inline( always ) ] + /// fn entry_to_val( e : Self::Entry ) -> Self::Val + /// { + /// e + /// } + /// + /// } + /// + /// impl IntoIterator for MyContainer + /// { + /// type Item = i32; + /// type IntoIter = std::vec::IntoIter< i32 >; + /// // type IntoIter = collection_tools::vec::IntoIter< i32 >; + /// // qqq : zzz : make sure collection_tools has itearators + /// + /// fn into_iter( self ) -> Self::IntoIter + /// { + /// self.entries.into_iter() // Create an iterator from the internal HashSet. + /// } + /// } + /// + /// impl ContainerAssign for MyContainer + /// { + /// fn assign< Entries >( &mut self, entries : Entries ) -> usize + /// where + /// Entries : IntoIterator< Item = Self::Entry >, + /// { + /// self.entries.clear(); + /// self.entries.extend( entries ); + /// self.entries.len() + /// } + /// } + /// + /// let mut container = MyContainer { entries : vec![ 1, 2, 3 ] }; + /// let new_elements = vec![ 4, 5, 6 ]; + /// assert_eq!( container.assign( new_elements ), 3 ); // Container now contains [ 4, 5, 6 ] + /// ``` + fn assign< Entries >( &mut self, entries : Entries ) -> usize + where + Entries : IntoIterator< Item = Self::Entry >; + } -} + // = -impl< T > ContainerAdd for collection_tools::Vec< T > -{ - type Element = T; + /// A builder structure for constructing containers with a fluent and flexible interface. + #[ derive( Default ) ] + pub struct ContainerFormer< E, Definition > + where + Definition : FormerDefinition, + Definition::Storage : ContainerAdd< Entry = E >, + { + storage : Definition::Storage, + context : core::option::Option< Definition::Context >, + on_end : core::option::Option< Definition::End >, + } - #[ inline( always ) ] - fn add( &mut self, e : Self::Element ) -> bool + use core::fmt; + impl< E, Definition > fmt::Debug for ContainerFormer< E, Definition > + where + Definition : FormerDefinition, + Definition::Storage : ContainerAdd< Entry = E >, { - self.push( e ); - true + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + f + .debug_struct( "ContainerFormer" ) + .field( "storage", &"Storage Present" ) + .field( "context", &self.context.as_ref().map( |_| "Context Present" ) ) + .field( "on_end", &self.on_end.as_ref().map( |_| "End Present" ) ) + .finish() + } } -} + impl< E, Definition > ContainerFormer< E, Definition > + where + Definition : FormerDefinition, + Definition::Storage : ContainerAdd< Entry = E >, + { + /// Begins the construction process of a container with optional initial storage and context, + /// setting up an `on_end` completion handler to finalize the container's construction. + #[ inline( always ) ] + pub fn begin + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : Definition::End, + ) + -> Self + { + if storage.is_none() + { + storage = Some( core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context, + on_end : Some( on_end ), + } + } -impl< E > ContainerAdd for collection_tools::HashSet< E > -where - E : core::cmp::Eq + core::hash::Hash, -{ - type Element = E; + /// Provides a variation of the `begin` method allowing for coercion of the end handler, + /// facilitating ease of integration with different end conditions. + #[ inline( always ) ] + pub fn begin_coercing< IntoEnd > + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : IntoEnd, + ) + -> Self + where + IntoEnd : Into< Definition::End >, + { + if storage.is_none() + { + storage = Some( core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context, + on_end : Some( on_end.into() ), + } + } + + /// Finalizes the building process, returning the formed or a context incorporating it. + #[ inline( always ) ] + pub fn end( mut self ) -> Definition::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + on_end.call( self.storage, context ) + } - #[ inline( always ) ] - fn add( &mut self, e : Self::Element ) -> bool + /// Alias for the `end` method to align with typical builder pattern terminologies. + #[ inline( always ) ] + pub fn form( self ) -> Definition::Formed + { + self.end() + } + + /// Replaces the current storage with a provided storage, allowing for resetting or + /// redirection of the building process. + #[ inline( always ) ] + pub fn replace( mut self, storage : Definition::Storage ) -> Self + { + self.storage = storage; + self + } + } + + impl< E, Storage, Formed, Definition > ContainerFormer< E, Definition > + where + Definition : FormerDefinition< Context = (), Storage = Storage, Formed = Formed >, + Definition::Storage : ContainerAdd< Entry = E >, { - self.insert( e ) + /// Constructs a new `ContainerFormer` instance, starting with an empty storage. + /// This method serves as the entry point for the builder pattern, facilitating the + /// creation of a new container. + #[ inline( always ) ] + pub fn new( end : Definition::End ) -> Self + { + Self::begin + ( + None, + None, + end, + ) + } + + /// Variant of the `new` method allowing for end condition coercion, providing flexibility + /// in specifying different types of end conditions dynamically. + #[ inline( always ) ] + pub fn new_coercing< IntoEnd >( end : IntoEnd ) -> Self + where + IntoEnd : Into< Definition::End >, + { + Self::begin + ( + None, + None, + end.into(), + ) + } } -} + impl< E, Definition > ContainerFormer< E, Definition > + where + Definition : FormerDefinition, + Definition::Storage : ContainerAdd< Entry = E >, + { -impl< K, V > ContainerAdd for collection_tools::HashMap< K, V > -where - K : core::cmp::Eq + core::hash::Hash, -{ - type Element = ( K, V ); + /// Appends an entry to the end of the storage, expanding the internal collection. + #[ inline( always ) ] + pub fn add< IntoElement >( mut self, entry : IntoElement ) -> Self + where IntoElement : core::convert::Into< E >, + { + ContainerAdd::add( &mut self.storage, entry.into() ); + self + } + + } - #[ inline( always ) ] - fn add( &mut self, ( k, v ) : Self::Element ) -> bool + // + + impl< E, Definition > FormerBegin< Definition > + for ContainerFormer< E, Definition > + where + Definition : FormerDefinition, + Definition::Storage : ContainerAdd< Entry = E >, { - self.insert( k, v ).map_or_else( || true, | _ | false ) + + #[ inline( always ) ] + fn former_begin + ( + storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : Definition::End, + ) + -> Self + { + Self::begin( storage, context, on_end ) + } + } } -// qqq : implement for other containers +/// Former of a vector. +mod vector; +/// Former of a hash map. +mod hash_map; +/// Former of a hash set. +mod hash_set; -/// A trait defining the capability to replface all elements. -pub trait ContainerAssign -{ - /// The type of elements to be added to the container. - type Element; +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; - /// Agging elements to the container. - fn assign< Elements >( &mut self, elements : Elements ) -> usize - where - Elements : IntoIterator< Item = Self::Element >; +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; +} +/// Parented namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; } -impl< T > ContainerAssign for collection_tools::Vec< T > +/// Exposed namespace of the module. +pub mod exposed { - type Element = T; - #[ inline( always ) ] - fn assign< Elements >( &mut self, elements : Elements ) -> usize - where - Elements : IntoIterator< Item = Self::Element > + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::prelude::*; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: { - let initial_len = self.len(); - self.extend( elements ); - self.len() - initial_len - } -} + EntryToVal, + ContainerValToEntry, + ValToEntry, -impl< T > ContainerAssign for collection_tools::HashSet< T > -where - T : core::cmp::Eq + core::hash::Hash, -{ - type Element = T; + Container, + ContainerAdd, + ContainerAssign, + ContainerFormer, - fn assign< Elements >( &mut self, elements : Elements ) -> usize - where - Elements : IntoIterator< Item = Self::Element > + }; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super:: { - let initial_len = self.len(); - self.extend( elements ); - self.len() - initial_len - } + vector::*, + hash_map::*, + hash_set::*, + }; + } -impl< K, V > ContainerAssign for collection_tools::HashMap< K, V > -where - K : core::cmp::Eq + core::hash::Hash, +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude { - type Element = ( K, V ); - - fn assign< Elements >( &mut self, elements : Elements ) -> usize - where - Elements : IntoIterator< Item = Self::Element > - { - let initial_len = self.len(); - self.extend( elements ); - self.len() - initial_len - } } diff --git a/module/core/former/src/container/hash_map.rs b/module/core/former/src/container/hash_map.rs new file mode 100644 index 0000000000..6054850903 --- /dev/null +++ b/module/core/former/src/container/hash_map.rs @@ -0,0 +1,252 @@ +//! This module provides a comprehensive approach to applying the builder pattern to `HashMap` containers. +//! +//! By leveraging traits such as `Container`, `ContainerAdd`, `ContainerAssign`, and `ContainerValToEntry`, +//! this module abstracts the operations on hashmap-like data structures, making them more flexible and easier to integrate as +//! as subformer, enabling fluid and intuitive manipulation of hashmaps via builder patterns. +//! + +use crate::*; +use collection_tools::HashMap; + +impl< K, V > Container for collection_tools::HashMap< K, V > +where + K : core::cmp::Eq + core::hash::Hash, +{ + type Entry = ( K, V ); + type Val = V; + + #[ inline( always ) ] + fn entry_to_val( e : Self::Entry ) -> Self::Val + { + e.1 + } + +} + +impl< K, V > ContainerAdd for collection_tools::HashMap< K, V > +where + K : core::cmp::Eq + core::hash::Hash, +{ + + #[ inline( always ) ] + fn add( &mut self, ( k, v ) : Self::Entry ) -> bool + { + self.insert( k, v ).map_or_else( || true, | _ | false ) + } + +} + +impl< K, V > ContainerAssign for collection_tools::HashMap< K, V > +where + K : core::cmp::Eq + core::hash::Hash, +{ + + fn assign< Elements >( &mut self, elements : Elements ) -> usize + where + Elements : IntoIterator< Item = Self::Entry > + { + let initial_len = self.len(); + self.extend( elements ); + self.len() - initial_len + } +} + +// = storage + +impl< K, E > Storage +for HashMap< K, E > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Preformed = HashMap< K, E >; +} + +impl< K, E > StoragePreform +for HashMap< K, E > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + fn preform( self ) -> Self::Preformed + { + self + } +} + +// = definition + +/// Represents the formation definition for a hash map-like container within the former framework. +/// +/// This structure defines the essential elements required to form a hash map-like container, detailing +/// the key and value types, the contextual environment during formation, the final formed type, and the +/// behavior at the end of the formation process. It facilitates customization and extension of hash map +/// formation within any system that implements complex data management operations. +/// +/// # Type Parameters +/// - `K`: The key type of the hash map. +/// - `E`: The value type of the hash map. +/// - `Context`: The optional context provided during the formation process. +/// - `Formed`: The type of the entity produced, typically a `HashMap`. +/// - `End`: A trait defining the end behavior of the formation process, managing how the hash map is finalized. +/// + +#[ derive( Debug, Default ) ] +pub struct HashMapDefinition< K, E, Context = (), Formed = HashMap< K, E >, End = ReturnStorage > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : FormingEnd< HashMapDefinitionTypes< K, E, Context, Formed > >, +{ + _phantom : core::marker::PhantomData< ( K, E, Context, Formed, End ) >, +} + +impl< K, E, Context, Formed, End > FormerDefinition +for HashMapDefinition< K, E, Context, Formed, End > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : FormingEnd< HashMapDefinitionTypes< K, E, Context, Formed > >, +{ + + type Storage = HashMap< K, E >; + type Formed = Formed; + type Context = Context; + + type Types = HashMapDefinitionTypes< K, E, Context, Formed >; + type End = End; + +} + +// = definition types + +/// Holds the generic parameters for the `HashMapDefinition`. +/// +/// This companion struct to `HashMapDefinition` defines the storage type and the context, along with the +/// type that is ultimately formed through the process. It is crucial for maintaining the integrity and +/// consistency of type relations throughout the former lifecycle. +/// +/// # Type Parameters +/// - `K`: The key type of the hash map. +/// - `E`: The value type of the hash map. +/// - `Context`: The operational context in which the hash map is formed. +/// - `Formed`: The type produced, typically mirroring the structure of a `HashMap`. + +#[ derive( Debug, Default ) ] +pub struct HashMapDefinitionTypes< K, E, Context = (), Formed = HashMap< K, E > > +{ + _phantom : core::marker::PhantomData< ( K, E, Context, Formed ) >, +} + +impl< K, E, Context, Formed > FormerDefinitionTypes +for HashMapDefinitionTypes< K, E, Context, Formed > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Storage = HashMap< K, E >; + type Formed = Formed; + type Context = Context; +} + +// = mutator + +impl< K, E, Context, Formed > FormerMutator +for HashMapDefinitionTypes< K, E, Context, Formed > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ +} + +// = Entity To + +impl< K, E, Definition > EntityToFormer< Definition > for HashMap< K, E > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + Definition : FormerDefinition + < + Storage = HashMap< K, E >, + Types = HashMapDefinitionTypes + < + K, + E, + < Definition as definition::FormerDefinition >::Context, + < Definition as definition::FormerDefinition >::Formed, + >, + >, + Definition::End : forming::FormingEnd< Definition::Types >, +{ + type Former = HashMapFormer< K, E, Definition::Context, Definition::Formed, Definition::End >; +} + +impl< K, E > crate::EntityToStorage +for HashMap< K, E > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Storage = HashMap< K, E >; +} + +impl< K, E, Context, Formed, End > crate::EntityToDefinition< Context, Formed, End > +for HashMap< K, E > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : crate::FormingEnd< HashMapDefinitionTypes< K, E, Context, Formed > >, +{ + type Definition = HashMapDefinition< K, E, Context, Formed, End >; + type Types = HashMapDefinitionTypes< K, E, Context, Formed >; +} + +impl< K, E, Context, Formed > crate::EntityToDefinitionTypes< Context, Formed > +for HashMap< K, E > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Types = HashMapDefinitionTypes< K, E, Context, Formed >; +} + +// = subformer + +/// Provides a streamlined builder interface for constructing hash map-like containers. +/// +/// `HashMapFormer` is a type alias that configures the `ContainerFormer` specifically for hash maps, +/// facilitating a more intuitive and flexible way to build and manipulate hash maps within custom data structures. +/// This type alias simplifies the usage of hash maps in builder patterns by encapsulating complex generic parameters +/// and leveraging the `HashMapDefinition` to handle the construction logic. It supports fluent chaining of key-value +/// insertions and can be customized with various end actions to finalize the hash map upon completion. +/// +/// The alias helps reduce boilerplate code and enhances readability, making the construction of hash maps in +/// a builder pattern both efficient and expressive. + +pub type HashMapFormer< K, E, Context, Formed, End > = +ContainerFormer::< ( K, E ), HashMapDefinition< K, E, Context, Formed, End > >; + +// = extension + +/// Provides an extension method for hash maps to facilitate the use of the builder pattern. +/// +/// This trait extends the `HashMap` type, enabling it to use the `HashMapFormer` interface directly. +/// It allows for fluent, expressive construction and manipulation of hash maps, integrating seamlessly +/// with the builder pattern provided by the `former` framework. It's a convenience trait that simplifies +/// creating configured hash map builders with default settings. +/// + +pub trait HashMapExt< K, E > : sealed::Sealed +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + /// Initializes a builder pattern for `HashMap` using a default `HashMapFormer`. + fn former() -> HashMapFormer< K, E, (), HashMap< K, E >, ReturnStorage >; +} + +impl< K, E > HashMapExt< K, E > for HashMap< K, E > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + fn former() -> HashMapFormer< K, E, (), HashMap< K, E >, ReturnStorage > + { + HashMapFormer::< K, E, (), HashMap< K, E >, ReturnStorage >::new( ReturnStorage::default() ) + } +} + +mod sealed +{ + use super::HashMap; + pub trait Sealed {} + impl< K, E > Sealed for HashMap< K, E > {} +} diff --git a/module/core/former/src/container/hash_set.rs b/module/core/former/src/container/hash_set.rs new file mode 100644 index 0000000000..6e96684ee1 --- /dev/null +++ b/module/core/former/src/container/hash_set.rs @@ -0,0 +1,278 @@ +//! This module provides a builder pattern implementation (`HashSetFormer`) for `HashSet`-like containers. It is designed to extend the builder pattern, allowing for fluent and dynamic construction of sets within custom data structures. + +use crate::*; +use collection_tools::HashSet; + +impl< K > Container for collection_tools::HashSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + type Entry = K; + type Val = K; + + #[ inline( always ) ] + fn entry_to_val( e : Self::Entry ) -> Self::Val + { + e + } + +} + +impl< K > ContainerAdd for collection_tools::HashSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + // type Entry = K; + // type Val = K; + + #[ inline( always ) ] + fn add( &mut self, e : Self::Entry ) -> bool + { + self.insert( e ) + } + +} + +impl< K > ContainerAssign for collection_tools::HashSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + // type Entry = K; + + fn assign< Elements >( &mut self, elements : Elements ) -> usize + where + Elements : IntoIterator< Item = Self::Entry > + { + let initial_len = self.len(); + self.extend( elements ); + self.len() - initial_len + } +} + +impl< K > ContainerValToEntry< K > for HashSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + type Entry = K; + #[ inline( always ) ] + fn val_to_entry( val : K ) -> Self::Entry + { + val + } +} + +// /// A trait for containers behaving like a `HashSet`, allowing insertion operations. +// /// +// /// Implementing this trait enables the associated formed to be used with `HashSetFormer`, +// /// facilitating a builder pattern that is both intuitive and concise. +// /// +// /// # Example Implementation +// /// +// /// Implementing `HashSetLike` for `std::collections::HashSet`: +// /// +// +// pub trait HashSetLike< K > +// where +// K : core::cmp::Eq + core::hash::Hash, +// { +// /// Inserts a key-value pair into the map. +// fn insert( &mut self, element : K ) -> Option< K >; +// } +// +// // impl< K > HashSetLike< K > for HashSet< K > +// // where +// // K : core::cmp::Eq + core::hash::Hash, +// // { +// // fn insert( &mut self, element : K ) -> Option< K > +// // { +// // HashSet::replace( self, element ) +// // } +// // } + +// = storage + +impl< K > Storage +for HashSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + // type Formed = HashSet< K >; + type Preformed = HashSet< K >; +} + +impl< K > StoragePreform +for HashSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + // type Preformed = HashSet< K >; + fn preform( self ) -> Self::Preformed + { + self + } +} + +// = definition + +/// Represents the formation definition for a hash set-like container within the former framework. +/// +/// This structure defines the essential elements required to form a hash set-like container, detailing +/// the type of elements, the contextual environment during formation, the final formed type, and the +/// behavior at the end of the formation process. It is designed to support the construction and configuration +/// of hash set containers with dynamic characteristics and behaviors. +/// +/// # Type Parameters +/// - `K`: The type of elements in the hash set. +/// - `Context`: The optional context provided during the formation process. +/// - `Formed`: The type of the entity produced, typically a `HashSet`. +/// - `End`: A trait defining the end behavior of the formation process, managing how the hash set is finalized. +/// + +#[ derive( Debug, Default ) ] +pub struct HashSetDefinition< K, Context = (), Formed = HashSet< K >, End = ReturnStorage > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : FormingEnd< HashSetDefinitionTypes< K, Context, Formed > >, +{ + _phantom : core::marker::PhantomData< ( K, Context, Formed, End ) >, +} + +impl< K, Context, Formed, End > FormerDefinition +for HashSetDefinition< K, Context, Formed, End > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : FormingEnd< HashSetDefinitionTypes< K, Context, Formed > >, +{ + type Storage = HashSet< K >; + type Formed = Formed; + type Context = Context; + + type Types = HashSetDefinitionTypes< K, Context, Formed >; + type End = End; +} + +// = definition types + +/// Holds the generic parameters for the `HashSetDefinition`. +/// +/// This struct encapsulates the type relationships and characteristics essential for the formation process +/// of a `HashSet`, including the storage type, the context, and the type ultimately formed. It ensures that +/// these elements are congruent and coherent throughout the lifecycle of the hash set formation. +/// + +#[ derive( Debug, Default ) ] +pub struct HashSetDefinitionTypes< K, Context = (), Formed = HashSet< K > > +{ + _phantom : core::marker::PhantomData< ( K, Context, Formed ) >, +} + +impl< K, Context, Formed > FormerDefinitionTypes +for HashSetDefinitionTypes< K, Context, Formed > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Storage = HashSet< K >; + type Formed = Formed; + type Context = Context; +} + +// = mutator + +impl< K, Context, Formed > FormerMutator +for HashSetDefinitionTypes< K, Context, Formed > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ +} + +// = entity to + +impl< K, Definition > EntityToFormer< Definition > for HashSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + Definition : FormerDefinition + < + Storage = HashSet< K >, + Types = HashSetDefinitionTypes + < + K, + < Definition as definition::FormerDefinition >::Context, + < Definition as definition::FormerDefinition >::Formed, + >, + >, + Definition::End : forming::FormingEnd< Definition::Types >, +{ + type Former = HashSetFormer< K, Definition::Context, Definition::Formed, Definition::End >; +} + +impl< K > crate::EntityToStorage +for HashSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Storage = HashSet< K >; +} + +impl< K, Context, Formed, End > crate::EntityToDefinition< Context, Formed, End > +for HashSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : crate::FormingEnd< HashSetDefinitionTypes< K, Context, Formed > >, +{ + type Definition = HashSetDefinition< K, Context, Formed, End >; + type Types = HashSetDefinitionTypes< K, Context, Formed >; +} + +impl< K, Context, Formed > crate::EntityToDefinitionTypes< Context, Formed > +for HashSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Types = HashSetDefinitionTypes< K, Context, Formed >; +} + +// = subformer + +/// Provides a concise alias for `ContainerFormer` configured specifically for `HashSet`-like containers. +/// +/// `HashSetFormer` simplifies the creation of `HashSet` containers within builder patterns by leveraging +/// the `ContainerFormer` with predefined settings. This approach minimizes boilerplate code and enhances +/// readability, making it ideal for fluent and expressive construction of set containers within custom data structures. +/// + +pub type HashSetFormer< K, Context, Formed, End > = +ContainerFormer::< K, HashSetDefinition< K, Context, Formed, End > >; + +// = extension + +/// Provides an extension method for `HashSet` to facilitate the use of the builder pattern. +/// +/// This trait extends `HashSet`, enabling direct use of the `HashSetFormer` interface for fluent and expressive +/// set construction. It simplifies the process of building `HashSet` instances by providing a straightforward +/// way to start the builder pattern with default context and termination behavior. +/// + +pub trait HashSetExt< K > : sealed::Sealed +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + /// Initializes a builder pattern for `HashSet` using a default `HashSetFormer`. + fn former() -> HashSetFormer< K, (), HashSet< K >, ReturnStorage >; +} + +impl< K > HashSetExt< K > for HashSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + fn former() -> HashSetFormer< K, (), HashSet< K >, ReturnStorage > + { + HashSetFormer::< K, (), HashSet< K >, ReturnStorage >::new( ReturnStorage::default() ) + } +} + +mod sealed +{ + use super::HashSet; + pub trait Sealed {} + impl< K > Sealed for HashSet< K > {} +} diff --git a/module/core/former/src/container/vector.rs b/module/core/former/src/container/vector.rs new file mode 100644 index 0000000000..33c344d1ab --- /dev/null +++ b/module/core/former/src/container/vector.rs @@ -0,0 +1,234 @@ +//! This module provides a comprehensive approach to applying the builder pattern to `Vec` containers. +//! +//! By leveraging traits such as `Container`, `ContainerAdd`, `ContainerAssign`, and `ContainerValToEntry`, +//! this module abstracts the operations on vector-like data structures, making them more flexible and easier to integrate as +//! as subformer, enabling fluid and intuitive manipulation of vectors via builder patterns. +//! + +use crate::*; +#[ allow( unused ) ] +use collection_tools::Vec; + +impl< E > Container for collection_tools::Vec< E > +{ + type Entry = E; + type Val = E; + + #[ inline( always ) ] + fn entry_to_val( e : Self::Entry ) -> Self::Val + { + e + } + +} + +impl< E > ContainerAdd for collection_tools::Vec< E > +{ + + #[ inline( always ) ] + fn add( &mut self, e : Self::Entry ) -> bool + { + self.push( e ); + true + } + +} + +impl< E > ContainerAssign for collection_tools::Vec< E > +{ + #[ inline( always ) ] + fn assign< Elements >( &mut self, elements : Elements ) -> usize + where + Elements : IntoIterator< Item = Self::Entry > + { + let initial_len = self.len(); + self.extend( elements ); + self.len() - initial_len + } + +} + +impl< E > ContainerValToEntry< E > for collection_tools::Vec< E > +where +{ + type Entry = E; + #[ inline( always ) ] + fn val_to_entry( val : E ) -> Self::Entry + { + val + } +} + +// = storage + +impl< E > Storage +for Vec< E > +{ + type Preformed = Vec< E >; +} + +impl< E > StoragePreform +for Vec< E > +{ + fn preform( self ) -> Self::Preformed + { + self + } +} + +// = definition + +/// Represents the formation definition for a vector-like container within the former framework. +/// +/// This structure defines the necessary parameters and relationships needed to form a vector-like container, +/// including its storage, context, the result of the formation process, and the behavior at the end of the formation. +/// +/// # Type Parameters +/// - `E`: The element type of the vector. +/// - `Context`: The context needed for the formation, can be provided externally. +/// - `Formed`: The type formed at the end of the formation process, typically a `Vec`. +/// - `End`: A trait determining the behavior at the end of the formation process. +/// + +#[ derive( Debug, Default ) ] +pub struct VectorDefinition< E, Context, Formed, End > +where + End : FormingEnd< VectorDefinitionTypes< E, Context, Formed > >, +{ + _phantom : core::marker::PhantomData< ( E, Context, Formed, End ) >, +} + +impl< E, Context, Formed, End > FormerDefinition +for VectorDefinition< E, Context, Formed, End > +where + End : FormingEnd< VectorDefinitionTypes< E, Context, Formed > >, +{ + type Storage = Vec< E >; + type Context = Context; + type Formed = Formed; + + type Types = VectorDefinitionTypes< E, Context, Formed >; + type End = End; +} + +// = definition type + +/// Holds the generic parameters for the `VectorDefinition`. +/// +/// This struct acts as a companion to `VectorDefinition`, providing a concrete definition of types used +/// in the formation process. It is crucial for linking the type parameters with the operational mechanics +/// of the formation and ensuring type safety and correctness throughout the formation lifecycle. +/// +/// # Type Parameters +/// +/// - `E`: The element type of the vector. +/// - `Context`: The context in which the vector is formed. +/// - `Formed`: The type produced as a result of the formation process. + +#[ derive( Debug, Default ) ] +pub struct VectorDefinitionTypes< E, Context = (), Formed = Vec< E > > +{ + _phantom : core::marker::PhantomData< ( E, Context, Formed ) >, +} + +impl< E, Context, Formed > FormerDefinitionTypes +for VectorDefinitionTypes< E, Context, Formed > +{ + type Storage = Vec< E >; + type Context = Context; + type Formed = Formed; +} + +// = mutator + +impl< E, Context, Formed > FormerMutator +for VectorDefinitionTypes< E, Context, Formed > +{ +} + +// = Entity To + +impl< E, Definition > EntityToFormer< Definition > +for Vec< E > +where + Definition : FormerDefinition + < + Storage = Vec< E >, + Types = VectorDefinitionTypes + < + E, + < Definition as definition::FormerDefinition >::Context, + < Definition as definition::FormerDefinition >::Formed, + >, + >, + Definition::End : forming::FormingEnd< Definition::Types >, +{ + type Former = VectorFormer< E, Definition::Context, Definition::Formed, Definition::End >; +} + +impl< E > crate::EntityToStorage +for Vec< E > +{ + type Storage = Vec< E >; +} + +impl< E, Context, Formed, End > crate::EntityToDefinition< Context, Formed, End > +for Vec< E > +where + End : crate::FormingEnd< VectorDefinitionTypes< E, Context, Formed > >, +{ + type Definition = VectorDefinition< E, Context, Formed, End >; + type Types = VectorDefinitionTypes< E, Context, Formed >; +} + +impl< E, Context, Formed > crate::EntityToDefinitionTypes< Context, Formed > +for Vec< E > +{ + type Types = VectorDefinitionTypes< E, Context, Formed >; +} + +// = subformer + +/// Provides a streamlined builder interface for constructing vector-like containers. +/// +/// `VectorFormer` is a type alias that configures the `ContainerFormer` for use specifically with vectors. +/// It integrates the `VectorDefinition` to facilitate the fluent and dynamic construction of vectors, leveraging +/// predefined settings to reduce boilerplate code. This approach enhances readability and simplifies the use of +/// vectors in custom data structures where builder patterns are desired. +/// +/// The alias encapsulates complex generic parameters, making the construction process more accessible and maintainable. +/// It is particularly useful in scenarios where vectors are repeatedly used or configured in similar ways across different +/// parts of an application. +/// + +pub type VectorFormer< E, Context, Formed, End > = +ContainerFormer::< E, VectorDefinition< E, Context, Formed, End > >; + +// = extension + +/// Provides an extension method for vectors to facilitate the use of the builder pattern. +/// +/// This trait extends the `Vec` type, enabling it to use the `VectorFormer` interface directly. +/// This allows for fluent, expressive construction and manipulation of vectors, integrating seamlessly +/// with the builder pattern provided by the `former` framework. It's a convenience trait that simplifies +/// creating configured vector builders with default settings. +/// +pub trait VecExt< E > : sealed::Sealed +{ + /// Initializes a builder pattern for `Vec` using a default `VectorFormer`. + fn former() -> VectorFormer< E, (), Vec< E >, ReturnStorage >; +} + +impl< E > VecExt< E > for Vec< E > +{ + fn former() -> VectorFormer< E, (), Vec< E >, ReturnStorage > + { + VectorFormer::< E, (), Vec< E >, ReturnStorage >::new( ReturnStorage::default() ) + } +} + +mod sealed +{ + pub trait Sealed {} + impl< E > Sealed for super::Vec< E > {} +} diff --git a/module/core/former/src/definition.rs b/module/core/former/src/definition.rs new file mode 100644 index 0000000000..38563df0e8 --- /dev/null +++ b/module/core/former/src/definition.rs @@ -0,0 +1,100 @@ +//! Module `definition` +//! +//! Provides traits for defining the relationships between entities and their formation mechanisms. +//! These traits are central to the implementation of a flexible and extensible formation system, +//! enabling entities to be constructed using various configurations and complex logic. +//! +//! Key aspects of the module include: +//! - **Entity to Definition Mapping**: Linking entities to their specific formation definitions, +//! which detail how they are to be constructed. +//! - **Entity to Former Mapping**: Associating entities with formers that handle their construction +//! process. +//! - **Entity to Storage Mapping**: Defining the storage structures that maintain the state of an +//! entity during its formation. +//! - **Definition Traits**: Specifying the properties and ending conditions of the formation +//! process to ensure entities are formed according to specified rules and logic. +//! + +/// Maps a type of entity to its corresponding former definition. +/// This trait provides a linkage between the entity and its definition, +/// allowing the formation logic to understand what definition to apply +/// during the formation process. +pub trait EntityToDefinition< Context, Formed, End > +{ + /// The specific [`FormerDefinition`] associated with this entity. + type Definition : FormerDefinition; + /// The specific [`FormerDefinitionTypes`] associated with this entity. + type Types : FormerDefinitionTypes; +} + +/// Provides a mapping between a type of entity and its associated formation type definitions. +pub trait EntityToDefinitionTypes< Context, Formed > +{ + /// Specifies the `FormerDefinitionTypes` that define the storage, formed entity, and context types used during formation. + /// This association is essential for ensuring that the formation process is carried out with the correct type-specific logic. + type Types : FormerDefinitionTypes; +} + +/// Maps a type of entity to its corresponding former. +/// This trait binds an entity type to a specific former, facilitating the use +/// of custom formers in complex formation scenarios. +pub trait EntityToFormer< Definition > +where + Definition : FormerDefinition, +{ + /// The type of the former used for building the entity. + type Former; + + /// A placeholder function to reference the definition without operational logic to calm compiler. + fn __f(_: &Definition) {} +} + +/// Maps a type of entity to its storage type. +/// This trait defines what storage structure is used to hold the interim state +/// of an entity during its formation. +pub trait EntityToStorage +{ + /// The storage type used for forming the entity. + type Storage; +} + +/// Defines the fundamental components involved in the formation of an entity. +/// This trait specifies the types of storage, the formed entity, and the context +/// used during the formation process. +pub trait FormerDefinitionTypes : Sized +{ + /// The type of storage used to maintain the state during formation. + type Storage : Default; + + /// The type of the entity once fully formed. + type Formed; + + /// The contextual information used during formation, if any. + type Context; +} + +/// Expands on `FormerDefinitionTypes` by incorporating an ending mechanism for the formation process. +/// This trait connects the formation types with a specific endpoint, defining +/// how the formation process concludes, including any necessary transformations +/// or validations. +pub trait FormerDefinition : Sized +{ + /// Encapsulates the types related to the formation process including any mutators. + type Types : crate::FormerDefinitionTypes< Storage = Self::Storage, Formed = Self::Formed, Context = Self::Context > + + crate::FormerMutator; + + /// Defines the ending condition or operation of the formation process. + type End: crate::FormingEnd< Self::Types >; + + /// The storage type used during the formation. + type Storage : Default; + + /// The type of the entity being formed. It is + /// generally the structure for which the `Former` trait is derived, representing the fully formed + /// state of the entity. However, it can differ if a custom `FormingEnd` or a different `Formed` type + /// is defined to handle specific forming logic or requirements. + type Formed; + + /// The context used during the formation process. + type Context; +} diff --git a/module/core/former/src/forming.rs b/module/core/former/src/forming.rs new file mode 100644 index 0000000000..892f4ad526 --- /dev/null +++ b/module/core/former/src/forming.rs @@ -0,0 +1,286 @@ +//! Module `forming` +//! +//! This module defines a collection of traits that are crucial for implementing a structured and extensible builder pattern. +//! The traits provided manage the various stages of the forming process, handling the initiation, mutation, and completion +//! of constructing complex data structures. These traits facilitate the creation of flexible and maintainable formation +//! logic that can accommodate complex construction scenarios, including nested and conditional formations. + +/// Provides a mechanism for mutating the context and storage just before the forming process is completed. +/// +/// The `FormerMutator` trait allows for the implementation of custom mutation logic on the internal state +/// of an entity (context and storage) just before the final forming operation is completed. This mutation +/// occurs immediately before the `FormingEnd` callback is invoked. +/// +/// #### Differences from `FormingEnd` +/// +/// Unlike `FormingEnd`, which is responsible for integrating and finalizing the formation process of a field within +/// a parent former, `form_mutation` directly pertains to the entity itself. This method is designed to be independent +/// of whether the forming process is occurring within the context of a superformer or if the structure is a standalone +/// or nested field. This makes `form_mutation` suitable for entity-specific transformations that should not interfere +/// with the hierarchical forming logic managed by `FormingEnd`. +/// +/// #### Use Cases +/// +/// - Applying last-minute changes to the data being formed. +/// - Setting or modifying properties that depend on the final state of the storage or context. +/// - Storage-specific fields which are not present in formed structure. +/// +/// Look example `former_custom_mutator.rs` + +pub trait FormerMutator +where + Self : crate::FormerDefinitionTypes, +{ + /// Mutates the context and storage of the entity just before the formation process completes. + /// + /// This function is invoked immediately prior to the `FormingEnd` callback during the forming process. + /// It provides a hook for implementing custom logic that modifies the internal state (storage and context) + /// of the entity. `form_mutation` is particularly useful for adjustments or updates that need to reflect + /// in the entity just before it is finalized and returned. + /// + #[ inline ] + fn form_mutation( _storage : &mut Self::Storage, _context : &mut ::core::option::Option< Self::Context > ) + { + } +} + +// impl< Definition > crate::FormerMutator +// for Definition +// where +// Definition : crate::FormerDefinitionTypes, +// { +// } + +/// Defines a handler for the end of a subforming process, enabling the return of the original context. +/// +/// This trait is designed to be flexible, allowing for various end-of-forming behaviors in builder patterns. +/// Implementors can define how to transform or pass through the context during the forming process's completion. +/// +/// # Parameters +/// - `Storage`: The type of the container being processed. +/// - `Context`: The type of the context that might be altered or returned upon completion. + +pub trait FormingEnd< Definition : crate::FormerDefinitionTypes > +{ + /// Called at the end of the subforming process to return the modified or original context. + /// + /// # Parameters + /// - `container`: The container being processed. + /// - `context`: Optional context to be transformed or returned. + /// + /// # Returns + /// Returns the transformed or original context based on the implementation. + fn call( &self, storage : Definition::Storage, context : core::option::Option< Definition::Context > ) -> Definition::Formed; +} + +impl< Definition, F > FormingEnd< Definition > for F +where + F : Fn( Definition::Storage, core::option::Option< Definition::Context > ) -> Definition::Formed, + Definition : crate::FormerDefinitionTypes, +{ + #[ inline( always ) ] + fn call( &self, storage : Definition::Storage, context : core::option::Option< Definition::Context > ) -> Definition::Formed + { + self( storage, context ) + } +} + +/// A `FormingEnd` implementation that directly returns the formed container as the final product of the forming process. +/// +/// This struct is particularly useful when the end result of the forming process is simply the formed container itself, +/// without needing to integrate or process additional contextual information. It's ideal for scenarios where the final +/// entity is directly derived from the storage state without further transformations or context-dependent adjustments. +#[ derive( Debug, Default ) ] +pub struct ReturnPreformed; + +impl< Definition > FormingEnd< Definition > +for ReturnPreformed +where + Definition::Storage : crate::StoragePreform< Preformed = Definition::Formed >, + Definition : crate::FormerDefinitionTypes, +{ + /// Transforms the storage into its final formed state and returns it, bypassing context processing. + #[ inline( always ) ] + fn call( &self, storage : Definition::Storage, _context : core::option::Option< Definition::Context > ) -> Definition::Formed + { + crate::StoragePreform::preform( storage ) + } +} + +/// A `FormingEnd` implementation that returns the storage itself as the formed entity, disregarding any contextual data. +/// +/// This struct is suited for straightforward forming processes where the storage already represents the final state of the +/// entity, and no additional processing or transformation of the storage is required. It simplifies use cases where the +/// storage does not undergo a transformation into a different type at the end of the forming process. + +#[ derive( Debug, Default ) ] +pub struct ReturnStorage; + +impl< Definition, T > FormingEnd< Definition > +for ReturnStorage +where + Definition : crate::FormerDefinitionTypes< Context = (), Storage = T, Formed = T >, +{ + /// Returns the storage as the final product of the forming process, ignoring any additional context. + #[ inline( always ) ] + fn call( &self, storage : Definition::Storage, _context : core::option::Option< () > ) -> Definition::Formed + { + storage + } +} + +/// A placeholder `FormingEnd` used when no end operation is required or applicable. +/// +/// This implementation is useful in generic or templated scenarios where a `FormingEnd` is required by the interface, +/// but no meaningful end operation is applicable. It serves a role similar to `core::marker::PhantomData` by filling +/// generic parameter slots without contributing operational logic. +#[ derive( Debug, Default ) ] +pub struct NoEnd; + +impl< Definition > FormingEnd< Definition > +for NoEnd +where + Definition : crate::FormerDefinitionTypes, +{ + /// Intentionally causes a panic if called, as its use indicates a configuration error. + #[ inline( always ) ] + fn call( &self, _storage : Definition::Storage, _context : core::option::Option< Definition::Context > ) -> Definition::Formed + { + unreachable!(); + } +} + +#[ allow( unused_extern_crates ) ] +#[ cfg( all( feature = "no_std", feature = "use_alloc" ) ) ] +extern crate alloc; +#[ cfg( all( feature = "no_std", feature = "use_alloc" ) ) ] +use alloc::boxed::Box; + +/// A wrapper around a closure to be used as a `FormingEnd`. +/// +/// This struct allows for dynamic dispatch of a closure that matches the +/// `FormingEnd` trait's `call` method signature. It is useful for cases where +/// a closure needs to be stored or passed around as an object implementing +/// `FormingEnd`. +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +pub struct FormingEndClosure< Definition : crate::FormerDefinitionTypes > +{ + closure : Box< dyn Fn( Definition::Storage, Option< Definition::Context > ) -> Definition::Formed >, + _marker : core::marker::PhantomData< Definition::Storage >, +} + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +impl< T, Definition > From< T > for FormingEndClosure< Definition > +where + T : Fn( Definition::Storage, Option< Definition::Context > ) -> Definition::Formed + 'static, + Definition : crate::FormerDefinitionTypes, +{ + #[ inline( always ) ] + fn from( closure : T ) -> Self + { + Self + { + closure : Box::new( closure ), + _marker : core::marker::PhantomData + } + } +} + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +impl< Definition : crate::FormerDefinitionTypes > FormingEndClosure< Definition > +{ + /// Constructs a new `FormingEndClosure` with the provided closure. + /// + /// # Parameters + /// + /// * `closure` - A closure that matches the expected signature for transforming a container + /// and context into a new context. This closure is stored and called by the + /// `call` method of the `FormingEnd` trait implementation. + /// + /// # Returns + /// + /// Returns an instance of `FormingEndClosure` encapsulating the provided closure. + pub fn new( closure : impl Fn( Definition::Storage, Option< Definition::Context > ) -> Definition::Formed + 'static ) -> Self + { + Self + { + closure : Box::new( closure ), + _marker : core::marker::PhantomData + } + } +} + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +use core::fmt; +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +impl< Definition : crate::FormerDefinitionTypes > fmt::Debug for FormingEndClosure< Definition > +{ + fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result + { + f.debug_struct( "FormingEndClosure" ) + .field( "closure", &format_args!{ "- closure -" } ) + .field( "_marker", &self._marker ) + .finish() + } +} + +#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] +impl< Definition : crate::FormerDefinitionTypes > FormingEnd< Definition > +for FormingEndClosure< Definition > +{ + fn call( &self, storage : Definition::Storage, context : Option< Definition::Context > ) -> Definition::Formed + { + ( self.closure )( storage, context ) + } +} + +/// A trait for initiating a structured subforming process with contextual and intermediary storage linkage. +/// +/// This trait is crucial for the `derive(Former)` macro implementation, where it facilitates the creation +/// of a subformer that integrates seamlessly within a builder pattern chain. It handles intermediary storage +/// to accumulate state or data before finally transforming it into the final `Formed` structure. +/// +/// `FormerBegin` is particularly important in scenarios where complex, hierarchical structures are formed, +/// allowing a former to be reused within another former. This reusability and the ability to maintain both transient +/// state (`Storage`) and contextual information (`Context`) are essential for multi-step construction or transformation +/// processes that culminate in the generation of a final product (`Formed`). +/// +/// During code generation via the `derive(Former)` macro, `FormerBegin` provides the necessary scaffolding to +/// initiate the subforming process. This setup is critical for ensuring that all elements involved in the formation +/// are aligned from the onset, particularly when one former is nested within another, facilitating the creation +/// of complex hierarchical data structures. +/// + +pub trait FormerBegin< Definition : > +where + Definition : crate::FormerDefinition, +{ + + /// Launches the subforming process with an initial storage and context, setting up an `on_end` completion handler. + /// + /// This method initializes the formation process by providing the foundational elements necessary for + /// building the entity. It allows for the configuration of initial states and contextual parameters, which + /// are critical for accurately reflecting the intended final state of the entity. + /// + /// # Parameters + /// + /// * `storage` - An optional initial state for the intermediary storage structure. This parameter allows + /// for the pre-configuration of storage, which can be crucial for entities requiring specific initial states. + /// * `context` - An optional initial setting providing contextual information for the subforming process. + /// This context can influence how the formation process progresses, especially in complex forming scenarios. + /// * `on_end` - A completion handler responsible for transforming the accumulated `Storage` into the final `Formed` structure. + /// This parameter is vital for ensuring that the transition from `Storage` to `Formed` is handled correctly, + /// incorporating any last-minute adjustments or validations necessary for the entity's integrity. + /// + /// # Returns + /// + /// Returns an instance of Former. + /// + fn former_begin + ( + storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : Definition::End, + ) -> Self; + +} diff --git a/module/core/former/src/hash_map.rs b/module/core/former/src/hash_map.rs deleted file mode 100644 index 14c0831370..0000000000 --- a/module/core/former/src/hash_map.rs +++ /dev/null @@ -1,250 +0,0 @@ -use super::*; - -use collection_tools::HashMap; - -/// A trait for types that behave like hash maps, supporting insertion and custom forming behaviors. -/// -/// This trait allows for generic operations on hash map-like data structures, enabling the insertion -/// of key-value pairs and the creation of formers for more complex construction patterns. -/// -/// # Type Parameters -/// - `K`: The type of keys stored in the hash map. Must implement `Eq` and `Hash`. -/// - `E`: The type of elements (values) stored in the hash map. -pub trait HashMapLike< K, E > -where - K : core::cmp::Eq + core::hash::Hash, - Self : Sized + Default, -{ - - /// Inserts a key-value pair into the map. - fn insert( &mut self, k : K, e : E ) -> Option< E >; - - /// Return former. - #[ inline( always ) ] - fn former( self ) - -> HashMapSubformer< K, E, Self, Self, impl FormingEnd< Self, Self > > - { - HashMapSubformer::begin( Some( self ), None, ReturnFormed ) - } - - // /// Return former with a custom context. - // #[ inline( always ) ] - // fn former_begin< Context, End >( self, context : Context, end : End ) - // -> HashMapSubformer< K, E, Self, Context, End > - // where End : FormingEnd< Self, Context > - // { - // HashMapSubformer::begin( Some( self ), Some( context ), end ) - // } - -} - -impl< K, E > HashMapLike< K, E > for HashMap< K, E > -where - K : core::cmp::Eq + core::hash::Hash, - Self : Sized + Default, -{ - - #[ inline( always ) ] - fn insert( &mut self, k : K, e : E ) -> Option< E > - { - HashMap::insert( self, k, e ) - } - -} - -/// A builder for constructing hash map-like structures with a fluent interface. -/// -/// `HashMapSubformer` leverages the `HashMapLike` trait to enable a flexible and customizable -/// way to build hash map-like structures. It supports the chaining of insert operations and -/// allows for the definition of custom end actions to finalize the building process. -/// -/// # Type Parameters -/// - `K`: Key type, must implement `Eq` and `Hash`. -/// - `E`: Element (value) type. -/// - `Formed`: The hash map-like formed being built. -/// - `Context`: Type of the optional context used during the building process. -/// - `End`: End-of-forming action to be executed upon completion. -/// -/// # Examples -/// ``` -/// # #[ cfg( all( feature = "enabled", not( feature = "no_std" ) ) ) ] -/// # { -/// # use test_tools::exposed::*; -/// -/// #[ derive( Debug, PartialEq, former::Former ) ] -/// pub struct StructWithMap -/// { -/// #[ subformer( former::HashMapSubformer ) ] -/// map : std::collections::HashMap< &'static str, &'static str >, -/// } -/// -/// let struct1 = StructWithMap::former() -/// .map() -/// .insert( "a", "b" ) -/// .insert( "c", "d" ) -/// .end() -/// .form() -/// ; -/// assert_eq!( struct1, StructWithMap { map : hmap!{ "a" => "b", "c" => "d" } } ); -/// -/// # } -/// ``` - -#[ derive( Debug, Default ) ] -pub struct HashMapSubformer< K, E, Formed, Context, End > -where - K : core::cmp::Eq + core::hash::Hash, - Formed : HashMapLike< K, E > + core::default::Default, - End : FormingEnd< Formed, Context >, -{ - formed : core::option::Option< Formed >, - context : core::option::Option< Context >, - on_end : core::option::Option< End >, - _e_phantom : core::marker::PhantomData< E >, - _k_phantom : core::marker::PhantomData< K >, -} - -impl< K, E, Formed, Context, End > -HashMapSubformer< K, E, Formed, Context, End > -where - K : core::cmp::Eq + core::hash::Hash, - Formed : HashMapLike< K, E > + core::default::Default, - End : FormingEnd< Formed, Context >, -{ - - /// Form current former into target structure. - #[ inline( always ) ] - pub fn form( mut self ) -> Formed - { - let formed = if self.formed.is_some() - { - self.formed.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - formed - } - - /// Make a new HashMapSubformer. It should be called by a context generated for your structure. - /// The context is returned after completion of forming by function `on_end``. - #[ inline( always ) ] - pub fn begin - ( - formed : core::option::Option< Formed >, - context : core::option::Option< Context >, - on_end : End, - ) -> Self - { - Self - { - formed, - context, - on_end : Some( on_end ), - _e_phantom : core::marker::PhantomData, - _k_phantom : core::marker::PhantomData, - } - } - - /// Return context of your struct moving formed there. Should be called after configuring the formed. - #[ inline( always ) ] - pub fn end( mut self ) -> Context - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - /// Set the whole formed instead of setting each element individually. - #[ inline( always ) ] - pub fn replace( mut self, formed : Formed ) -> Self - { - self.formed = Some( formed ); - self - } - -} - -// impl< E, Formed > VectorSubformer< E, Formed, Formed, crate::ReturnFormed > -// where -// Formed : VectorLike< E > + core::default::Default, - -impl< K, E, Formed > -HashMapSubformer< K, E, Formed, Formed, crate::ReturnFormed > -where - K : core::cmp::Eq + core::hash::Hash, - Formed : HashMapLike< K, E > + core::default::Default, -{ - - /// Create a new instance without context or on end processing. It just returns continaer on end of forming. - #[ inline( always ) ] - pub fn new() -> Self - { - HashMapSubformer::begin - ( - None, - None, - crate::ReturnFormed, - ) - } - -} - -impl< K, E, Formed, Context, End > -HashMapSubformer< K, E, Formed, Context, End > -where - K : core::cmp::Eq + core::hash::Hash, - Formed : HashMapLike< K, E > + core::default::Default, - End : FormingEnd< Formed, Context >, -{ - - /// Inserts a key-value pair into the formed. If the formed doesn't exist, it is created. - /// - /// # Parameters - /// - `k`: The key for the value to be inserted. Will be converted into the formed's key type. - /// - `e`: The value to be inserted. Will be converted into the formed's value type. - /// - /// # Returns - /// Returns `self` for chaining further insertions or operations. - /// - #[ inline( always ) ] - pub fn insert< K2, E2 >( mut self, k : K2, e : E2 ) -> Self - where - K2 : core::convert::Into< K >, - E2 : core::convert::Into< E >, - { - if self.formed.is_none() - { - self.formed = core::option::Option::Some( Default::default() ); - } - if let core::option::Option::Some( ref mut formed ) = self.formed - { - formed.insert( k.into(), e.into() ); - } - self - } - - /// Alias for insert. - /// - /// # Parameters - /// - `k`: The key for the value to be inserted. Will be converted into the formed's key type. - /// - `e`: The value to be inserted. Will be converted into the formed's value type. - /// - /// # Returns - /// Returns `self` for chaining further insertions or operations. - /// - #[ inline( always ) ] - pub fn push< K2, E2 >( self, k : K2, e : E2 ) -> Self - where - K2 : core::convert::Into< K >, - E2 : core::convert::Into< E >, - { - self.insert( k, e ) - } - -} - -// diff --git a/module/core/former/src/hash_set.rs b/module/core/former/src/hash_set.rs deleted file mode 100644 index 20a521d3f6..0000000000 --- a/module/core/former/src/hash_set.rs +++ /dev/null @@ -1,244 +0,0 @@ -//! # HashSetLike Trait and HashSetSubformer Struct -//! -//! This part of the crate provides a flexible interface (`HashSetLike`) and a builder pattern implementation (`HashSetSubformer`) for `HashSet`-like containers. It's designed to extend the builder pattern, allowing for fluent and dynamic construction of sets within custom data structures. - -use super::*; -use collection_tools::HashSet; - -/// A trait for containers behaving like a `HashSet`, allowing insertion operations. -/// -/// Implementing this trait enables the associated formed to be used with `HashSetSubformer`, -/// facilitating a builder pattern that is both intuitive and concise. -/// -/// # Example Implementation -/// -/// Implementing `HashSetLike` for `std::collections::HashSet`: -/// - -pub trait HashSetLike< E > -where - E : core::cmp::Eq + core::hash::Hash, -{ - /// Inserts a key-value pair into the map. - fn insert( &mut self, element : E ) -> Option< E >; -} - -impl< E > HashSetLike< E > for HashSet< E > -where - E : core::cmp::Eq + core::hash::Hash, -{ - fn insert( &mut self, element : E ) -> Option< E > - { - HashSet::replace( self, element ) - } -} - -/// Facilitates building `HashSetLike` containers with a fluent API. -/// -/// `HashSetSubformer` leverages the `HashSetLike` trait to enable a concise and expressive way -/// of populating `HashSet`-like containers. It exemplifies the crate's builder pattern variation for sets. -/// -/// # Example Usage -/// -/// Using `HashSetSubformer` to populate a `HashSet` within a struct: -/// -/// ```rust -/// # #[ cfg( all( feature = "enabled", not( feature = "no_std" ) ) ) ] -/// # { -/// # use test_tools::exposed::*; -/// -/// #[ derive( Debug, PartialEq, former::Former ) ] -/// pub struct StructWithSet -/// { -/// #[ subformer( former::HashSetSubformer ) ] -/// set : std::collections::HashSet< &'static str >, -/// } -/// -/// let instance = StructWithSet::former() -/// .set() -/// .insert( "apple" ) -/// .insert( "banana" ) -/// .end() -/// .form(); -/// -/// assert_eq!(instance, StructWithSet { set : hset![ "apple", "banana" ] }); -/// # } -/// ``` - -#[ derive( Debug, Default ) ] -pub struct HashSetSubformer< E, Formed, Context, ContainerEnd > -where - E : core::cmp::Eq + core::hash::Hash, - Formed : HashSetLike< E > + core::default::Default, - ContainerEnd : FormingEnd< Formed, Context >, -{ - formed : core::option::Option< Formed >, - context : core::option::Option< Context >, - on_end : core::option::Option< ContainerEnd >, - _e_phantom : core::marker::PhantomData< E >, -} - -impl< E, Formed, Context, ContainerEnd > -HashSetSubformer< E, Formed, Context, ContainerEnd > -where - E : core::cmp::Eq + core::hash::Hash, - Formed : HashSetLike< E > + core::default::Default, - ContainerEnd : FormingEnd< Formed, Context >, -{ - - /// Form current former into target structure. - #[ inline( always ) ] - pub fn form( mut self ) -> Formed - { - let formed = if self.formed.is_some() - { - self.formed.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - formed - } - - /// Begins the building process with an optional context and formed. - /// - /// This method is typically called internally by the builder but can be used directly - /// to initialize the builder with specific contexts or containers. - /// - /// # Parameters - /// - `context`: An optional context for the building process. - /// - `formed`: An optional initial formed to populate. - /// - `on_end`: A handler to be called at the end of the building process. - /// - #[ inline( always ) ] - pub fn begin - ( - formed : core::option::Option< Formed >, - context : core::option::Option< Context >, - on_end : ContainerEnd, - ) -> Self - { - Self - { - formed, - context : context, - on_end : Some( on_end ), - _e_phantom : core::marker::PhantomData, - } - } - - /// Finalizes the building process and returns the constructed formed or a context. - /// - /// This method concludes the building process by applying the `on_end` handler to transform - /// the formed or incorporate it into a given context. It's typically called at the end - /// of the builder chain to retrieve the final product of the building process. - /// - /// # Returns - /// Depending on the `on_end` handler's implementation, this method can return either the - /// constructed formed or a context that incorporates the formed. - /// - #[ inline( always ) ] - pub fn end( mut self ) -> Context - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - /// Replaces the current formed with a new one. - /// - /// This method allows for replacing the entire set being built with a different one. - /// It can be useful in scenarios where a pre-populated set needs to be modified or - /// replaced entirely during the building process. - /// - /// # Parameters - /// - `formed`: The new formed to use for subsequent builder operations. - /// - /// # Returns - /// The builder instance with the formed replaced, enabling further chained operations. - /// - #[ inline( always ) ] - pub fn replace( mut self, formed : Formed ) -> Self - { - self.formed = Some( formed ); - self - } - -} - -// impl< E, Formed > VectorSubformer< E, Formed, Formed, crate::ReturnFormed > -// where -// Formed : VectorLike< E > + core::default::Default, -// { - -impl< E, Formed > -HashSetSubformer< E, Formed, Formed, crate::ReturnFormed > -where - E : core::cmp::Eq + core::hash::Hash, - Formed : HashSetLike< E > + core::default::Default, - // ContainerEnd : FormingEnd< Formed, Context >, -{ - - /// Initializes a new instance of the builder with default settings. - /// - /// This method provides a starting point for building a `HashSetLike` formed using - /// a fluent interface. It sets up an empty formed ready to be populated. - /// - /// # Returns - /// A new instance of `HashSetSubformer` with no elements. - /// - #[ inline( always ) ] - pub fn new() -> Self - { - HashSetSubformer::begin - ( - None, - None, - crate::ReturnFormed, - ) - } - -} - -impl< E, Formed, Context, ContainerEnd > -HashSetSubformer< E, Formed, Context, ContainerEnd > -where - E : core::cmp::Eq + core::hash::Hash, - Formed : HashSetLike< E > + core::default::Default, - ContainerEnd : FormingEnd< Formed, Context >, -{ - - /// Inserts an element into the set, possibly replacing an existing element. - /// - /// This method ensures that the set contains the given element, and if the element - /// was already present, it might replace it depending on the formed's behavior. - /// - /// # Parameters - /// - `element`: The element to insert into the set. - /// - /// # Returns - /// - `Some(element)` if the element was replaced. - /// - `None` if the element was newly inserted without replacing any existing element. - /// - #[ inline( always ) ] - pub fn insert< E2 >( mut self, element : E2 ) -> Self - where - E2 : core::convert::Into< E >, - { - if self.formed.is_none() - { - self.formed = core::option::Option::Some( Default::default() ); - } - if let core::option::Option::Some( ref mut formed ) = self.formed - { - formed.insert( element.into() ); - } - self - } - -} - -// \ No newline at end of file diff --git a/module/core/former/src/lib.rs b/module/core/former/src/lib.rs index c3fdf25c49..9653b8152e 100644 --- a/module/core/former/src/lib.rs +++ b/module/core/former/src/lib.rs @@ -4,43 +4,34 @@ #![ doc( html_root_url = "https://docs.rs/former/latest/former/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] -// xxx : describe "Context-aware forming process" - /// Axiomatic things. #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_former" ) ] mod axiomatic; - -/// Interface for containers. +/// Forming process. #[ cfg( feature = "enabled" ) ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] #[ cfg( feature = "derive_former" ) ] -mod container; -/// Former of a vector. +mod definition; +/// Forming process. #[ cfg( feature = "enabled" ) ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] #[ cfg( feature = "derive_former" ) ] -mod vector; -/// Former of a hash map. +mod forming; +/// Storage. #[ cfg( feature = "enabled" ) ] -#[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] #[ cfg( feature = "derive_former" ) ] -mod hash_map; -/// Former of a hash set. +mod storage; + +/// Interface for containers. #[ cfg( feature = "enabled" ) ] #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] #[ cfg( feature = "derive_former" ) ] -mod hash_set; +mod container; /// Component-based forming. #[ cfg( feature = "enabled" ) ] #[ cfg( any( feature = "derive_component_from", feature = "derive_component_assign" ) ) ] mod component; -// mod axiomatic2; -// mod vector2; -// mod vector3; - /// Namespace with dependencies. #[ cfg( feature = "enabled" ) ] pub mod dependency @@ -91,7 +82,13 @@ pub mod exposed #[ allow( unused_imports ) ] #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_former" ) ] - pub use super::axiomatic::*; + pub use super:: + { + axiomatic::*, + definition::*, + forming::*, + storage::*, + }; #[ doc( inline ) ] #[ allow( unused_imports ) ] @@ -99,24 +96,6 @@ pub mod exposed #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] #[ cfg( feature = "derive_former" ) ] pub use super::container::*; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - #[ cfg( feature = "enabled" ) ] - #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - #[ cfg( feature = "derive_former" ) ] - pub use super::vector::*; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - #[ cfg( feature = "enabled" ) ] - #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - #[ cfg( feature = "derive_former" ) ] - pub use super::hash_map::*; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - #[ cfg( feature = "enabled" ) ] - #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - #[ cfg( feature = "derive_former" ) ] - pub use super::hash_set::*; } @@ -130,5 +109,3 @@ pub mod prelude #[ cfg( any( feature = "derive_component_from", feature = "derive_component_assign" ) ) ] pub use super::component::*; } - -// xxx : explain role of container in former diff --git a/module/core/former/src/storage.rs b/module/core/former/src/storage.rs new file mode 100644 index 0000000000..02e20e78d5 --- /dev/null +++ b/module/core/former/src/storage.rs @@ -0,0 +1,49 @@ +//! Module `storage` +//! +//! Provides traits that define the storage mechanics used during the formation of entities in a builder pattern. +//! This module is critical for managing the state of entities as they are constructed, ensuring that all +//! interim data is handled appropriately before finalizing the entity's construction. +//! +//! Key components of the module include: +//! - **Storage Interface**: Defines the essential interface for any storage type used in the formation +//! process. It ensures that each storage type can be initialized to a default state. +//! - **Storage Preformation**: Outlines the method for transitioning storage from a mutable, intermediate +//! state to a finalized, immutable state of the entity. This is pivotal for concluding the formation process +//! with integrity and accuracy. +//! + +/// Defines the storage interface for entities being constructed using a forming pattern. +/// +/// This trait is required for any storage type that temporarily holds data during the construction +/// of an entity. It mandates the implementation of `Default`, ensuring that storage can be initialized +/// to a default state at the start of the forming process. +pub trait Storage : ::core::default::Default +{ + /// The type of the entity as it should appear once preformed. It could, but does not have to be the same type as `Formed`. + type Preformed; + // /// The type of the fully formed entity that results from the forming process. + // type Formed; +} + +/// Provides a mechanism to finalize the forming process by converting storage into its final formed state. +/// +/// This trait is crucial for transitioning the mutable, intermediate storage state into the final, +/// immutable state of an entity. The transformation is typically performed once all configurations +/// and modifications are applied to the storage during the forming process. The type `Preformed` and `Formed` is +/// generally the structure for which the `Former` trait is derived, representing the fully formed +/// state of the entity. However, it can differ if a custom `FormingEnd` or a different `Formed` type +/// is defined to handle specific forming logic or requirements. +/// But even if `Formed` is custom `Preformed` is always that structure. +pub trait StoragePreform : Storage +{ + // /// The type of the entity as it should appear once fully formed. + // type Preformed; + + /// Transforms the storage into the final formed state of the entity. + /// + /// This function is called at the conclusion of the forming process to finalize the entity's state, + /// effectively turning the mutable storage state into the immutable, fully formed entity. This transition + /// reflects the culmination of the forming process where the temporary, modifiable attributes of the + /// storage are solidified into the permanent attributes of the formed entity. + fn preform( self ) -> Self::Preformed; +} diff --git a/module/core/former/src/vector.rs b/module/core/former/src/vector.rs deleted file mode 100644 index 46426d8733..0000000000 --- a/module/core/former/src/vector.rs +++ /dev/null @@ -1,207 +0,0 @@ -use super::*; - -#[ allow( unused ) ] -use collection_tools::Vec; - -/// Trait for containers that behave like a vector, providing an interface for element addition. -/// -/// This trait enables the use of custom or standard vector-like containers within the builder pattern, -/// allowing for a unified and flexible approach to constructing collections. -/// -pub trait VectorLike< E > -{ - /// Appends an element to the back of a formed. - fn push( &mut self, element : E ); -} - -impl< E > VectorLike< E > for Vec< E > -{ - fn push( &mut self, element : E ) - { - Vec::push( self, element ); - } -} - -/// A builder for constructing `VectorLike` containers, facilitating a fluent and flexible interface. -/// -/// `VectorSubformer` leverages the `VectorLike` trait to enable the construction and manipulation -/// of vector-like containers in a builder pattern style, promoting readability and ease of use. -/// -/// # Example -/// ```rust -/// #[ derive( Debug, PartialEq, former::Former ) ] -/// pub struct StructWithVec -/// { -/// #[ subformer( former::VectorSubformer ) ] -/// vec : Vec< &'static str >, -/// } -/// -/// let instance = StructWithVec::former() -/// .vec() -/// .push( "apple" ) -/// .push( "banana" ) -/// .end() -/// .form(); -/// -/// assert_eq!( instance, StructWithVec { vec: vec![ "apple", "banana" ] } ); -///``` -/// -#[ derive( Debug, Default ) ] -pub struct VectorSubformer< E, Formed, Context, ContainerEnd > -where - Formed : VectorLike< E > + core::default::Default, - ContainerEnd : FormingEnd< Formed, Context >, -{ - formed : core::option::Option< Formed >, - context : core::option::Option< Context >, - on_end : core::option::Option< ContainerEnd >, - _phantom : core::marker::PhantomData< E >, -} - -impl< E, Formed, Context, ContainerEnd > VectorSubformer< E, Formed, Context, ContainerEnd > -where - Formed : VectorLike< E > + core::default::Default, - ContainerEnd : FormingEnd< Formed, Context >, -{ - - /// Form current former into target structure. - #[ inline( always ) ] - pub fn form( mut self ) -> Formed - { - let formed = if self.formed.is_some() - { - self.formed.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - formed - } - - // /// Initializes a new `VectorSubformer` instance, starting with an empty formed. - // /// This function serves as the entry point for the builder pattern. - // /// - // /// # Returns - // /// A new instance of `VectorSubformer` with an empty internal formed. - // /// - // #[ inline( always ) ] - // pub fn new() -> VectorSubformer< E, Formed, Formed, impl FormingEnd< Formed, Formed > > - // { - // VectorSubformer::begin - // ( - // None, - // None, - // crate::ReturnFormed, - // ) - // } - - /// Begins the building process, optionally initializing with a context and formed. - #[ inline( always ) ] - pub fn begin - ( - formed : core::option::Option< Formed >, - context : core::option::Option< Context >, - on_end : ContainerEnd - ) -> Self - { - Self - { - context, - formed, - on_end : Some( on_end ), - _phantom : core::marker::PhantomData, - } - } - - /// Finalizes the building process, returning the formed or a context incorporating it. - #[ inline( always ) ] - pub fn end( mut self ) -> Context - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - /// Replaces the current formed with a provided one, allowing for a reset or redirection of the building process. - #[ inline( always ) ] - pub fn replace( mut self, vector : Formed ) -> Self - { - self.formed = Some( vector ); - self - } - -} - -impl< E, Formed > VectorSubformer< E, Formed, Formed, crate::ReturnFormed > -where - Formed : VectorLike< E > + core::default::Default, -{ - - /// Initializes a new `VectorSubformer` instance, starting with an empty formed. - /// This function serves as the entry point for the builder pattern. - /// - /// # Returns - /// A new instance of `VectorSubformer` with an empty internal formed. - /// - #[ inline( always ) ] - pub fn new() -> Self - { - Self::begin - ( - None, - None, - crate::ReturnFormed, - ) - } - -} - -impl< E, Formed, Context, ContainerEnd > VectorSubformer< E, Formed, Context, ContainerEnd > -where - Formed : VectorLike< E > + core::default::Default, - ContainerEnd : FormingEnd< Formed, Context >, -{ - - /// Appends an element to the end of the formed, expanding the internal collection. - #[ inline( always ) ] - pub fn push< E2 >( mut self, element : E2 ) -> Self - where E2 : core::convert::Into< E >, - { - if self.formed.is_none() - { - self.formed = core::option::Option::Some( Default::default() ); - } - if let core::option::Option::Some( ref mut formed ) = self.formed - { - formed.push( element.into() ); - } - self - } - -} - -// - -impl< E, Formed, Context, End > FormerBegin< Formed, Formed, Context > -for VectorSubformer< E, Formed, Context, End > -where - End : FormingEnd< Formed, Context >, - Formed : VectorLike< E > + Default, -{ - type End = End; - - #[ inline( always ) ] - fn _begin - ( - formed : core::option::Option< Formed >, - context : core::option::Option< Context >, - on_end : End, - ) -> Self - { - Self::begin( formed, context, on_end ) - } - -} diff --git a/module/core/former/src/vector2.rs b/module/core/former/src/vector2.rs deleted file mode 100644 index 9c7e17beb5..0000000000 --- a/module/core/former/src/vector2.rs +++ /dev/null @@ -1,195 +0,0 @@ -use super::*; -use axiomatic2::*; - -#[ allow( unused ) ] -use collection_tools::Vec; - -/// Trait for containers that behave like a vector, providing an interface for element addition. -/// -/// This trait enables the use of custom or standard vector-like containers within the builder pattern, -/// allowing for a unified and flexible approach to constructing collections. -/// -pub trait VectorLike2< E > -{ - /// Appends an element to the back of a formed. - fn push( &mut self, element : E ); -} - -impl< E > VectorLike2< E > for Vec< E > -{ - fn push( &mut self, element : E ) - { - Vec::push( self, element ); - } -} - -impl< E > StoragePerform for Vec< E > -{ - type Formed = Self; - fn preform( self ) -> Self::Formed - { - self - } -} -pub struct VectorSubformerDescriptor< E > -{ - _phantom : core::marker::PhantomData< E >, -} - -impl< E > VectorSubformerDescriptor< E > -{ - fn new() -> Self - { - Self { _phantom : core::marker::PhantomData } - } -} - -impl< E > FormerDescriptor -for VectorSubformerDescriptor< E > -{ - type Storage = Vec< E >; - type Formed = Vec< E >; - // type Former = VectorSubformer2< E, Context, End >; -} - -/// A builder for constructing `VectorLike2` containers, facilitating a fluent and flexible interface. -/// -/// `VectorSubformer2` leverages the `VectorLike2` trait to enable the construction and manipulation -/// of vector-like containers in a builder pattern style, promoting readability and ease of use. -#[ derive( Debug, Default ) ] -pub struct VectorSubformer2< E, Context, End > -where - End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -{ - formed : core::option::Option< < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed >, - context : core::option::Option< Context >, - on_end : core::option::Option< End >, -} - -impl< E, Context, End > VectorSubformer2< E, Context, End > -where - End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -{ - - /// Form current former into target structure. - #[ inline( always ) ] - pub fn form( mut self ) -> < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed - { - let formed = if self.formed.is_some() - { - self.formed.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - formed - } - - /// Begins the building process, optionally initializing with a context and formed. - #[ inline( always ) ] - pub fn begin - ( - formed : core::option::Option< < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed >, - context : core::option::Option< Context >, - on_end : End - ) -> Self - { - Self - { - context, - formed, - on_end : Some( on_end ), - } - } - - /// Finalizes the building process, returning the formed or a context incorporating it. - #[ inline( always ) ] - pub fn end( mut self ) -> < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - /// Replaces the current formed with a provided one, allowing for a reset or redirection of the building process. - #[ inline( always ) ] - pub fn replace( mut self, vector : < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed ) -> Self - { - self.formed = Some( vector ); - self - } - -} - -impl< E > VectorSubformer2< E, (), ReturnStorage2 > -where -{ - - /// Initializes a new `VectorSubformer2` instance, starting with an empty formed. - /// This function serves as the entry point for the builder pattern. - /// - /// # Returns - /// A new instance of `VectorSubformer2` with an empty internal formed. - /// - #[ inline( always ) ] - pub fn new() -> Self - { - Self::begin - ( - None, - None, - ReturnStorage2, - ) - } - -} - -impl< E, Context, End > VectorSubformer2< E, Context, End > -where - End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -{ - - /// Appends an element to the end of the formed, expanding the internal collection. - #[ inline( always ) ] - pub fn push< E2 >( mut self, element : E2 ) -> Self - where E2 : core::convert::Into< E >, - { - if self.formed.is_none() - { - self.formed = core::option::Option::Some( Default::default() ); - } - if let core::option::Option::Some( ref mut formed ) = self.formed - { - formed.push( element.into() ); - } - self - } - -} - -// - -// impl< Former, Context, End > FormerBegin< Formed, Formed, Context > -// for VectorSubformer2< Former, Context, End > -// where -// End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -// // Formed : VectorLike2< E > + Default, -// Former : FormerDescriptor, -// { -// type End = End; -// -// #[ inline( always ) ] -// fn _begin -// ( -// formed : core::option::Option< Formed >, -// context : core::option::Option< Context >, -// on_end : End, -// ) -> Self -// { -// Self::begin( formed, context, on_end ) -// } -// -// } diff --git a/module/core/former/src/vector3.rs b/module/core/former/src/vector3.rs deleted file mode 100644 index 4df4958ce3..0000000000 --- a/module/core/former/src/vector3.rs +++ /dev/null @@ -1,196 +0,0 @@ -use super::*; -use axiomatic2::*; - -#[ allow( unused ) ] -use collection_tools::Vec; - -/// Trait for containers that behave like a vector, providing an interface for element addition. -/// -/// This trait enables the use of custom or standard vector-like containers within the builder pattern, -/// allowing for a unified and flexible approach to constructing collections. -/// -pub trait VectorLike2< E > -{ - /// Appends an element to the back of a formed. - fn push( &mut self, element : E ); -} - -impl< E > VectorLike2< E > for Vec< E > -{ - fn push( &mut self, element : E ) - { - Vec::push( self, element ); - } -} - -impl< E > StoragePerform for Vec< E > -{ - type Formed = Self; - fn preform( self ) -> Self::Formed - { - self - } -} -pub struct VectorSubformerDescriptor< E > -{ - _phantom : core::marker::PhantomData< E >, -} - -impl< E > VectorSubformerDescriptor< E > -{ - fn new() -> Self - { - Self { _phantom : core::marker::PhantomData } - } -} - -impl< E > FormerDescriptor -for VectorSubformerDescriptor< E > -{ - type Storage = Vec< E >; - type Formed = Vec< E >; - // type Former = VectorSubformer2< E, Context, End >; -} - -/// A builder for constructing `VectorLike2` containers, facilitating a fluent and flexible interface. -/// -/// `VectorSubformer2` leverages the `VectorLike2` trait to enable the construction and manipulation -/// of vector-like containers in a builder pattern style, promoting readability and ease of use. -/// -#[ derive( Debug, Default ) ] -pub struct VectorSubformer2< E, Context, End > -where - End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -{ - formed : core::option::Option< < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed >, - context : core::option::Option< Context >, - on_end : core::option::Option< End >, -} - -impl< E, Context, End > VectorSubformer2< E, Context, End > -where - End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -{ - - /// Form current former into target structure. - #[ inline( always ) ] - pub fn form( mut self ) -> < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed - { - let formed = if self.formed.is_some() - { - self.formed.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - formed - } - - /// Begins the building process, optionally initializing with a context and formed. - #[ inline( always ) ] - pub fn begin - ( - formed : core::option::Option< < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed >, - context : core::option::Option< Context >, - on_end : End - ) -> Self - { - Self - { - context, - formed, - on_end : Some( on_end ), - } - } - - /// Finalizes the building process, returning the formed or a context incorporating it. - #[ inline( always ) ] - pub fn end( mut self ) -> < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - /// Replaces the current formed with a provided one, allowing for a reset or redirection of the building process. - #[ inline( always ) ] - pub fn replace( mut self, vector : < VectorSubformerDescriptor< E > as axiomatic2::FormerDescriptor >::Formed ) -> Self - { - self.formed = Some( vector ); - self - } - -} - -impl< E > VectorSubformer2< E, (), ReturnStorage2 > -where -{ - - /// Initializes a new `VectorSubformer2` instance, starting with an empty formed. - /// This function serves as the entry point for the builder pattern. - /// - /// # Returns - /// A new instance of `VectorSubformer2` with an empty internal formed. - /// - #[ inline( always ) ] - pub fn new() -> Self - { - Self::begin - ( - None, - None, - ReturnStorage2, - ) - } - -} - -impl< E, Context, End > VectorSubformer2< E, Context, End > -where - End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -{ - - /// Appends an element to the end of the formed, expanding the internal collection. - #[ inline( always ) ] - pub fn push< E2 >( mut self, element : E2 ) -> Self - where E2 : core::convert::Into< E >, - { - if self.formed.is_none() - { - self.formed = core::option::Option::Some( Default::default() ); - } - if let core::option::Option::Some( ref mut formed ) = self.formed - { - formed.push( element.into() ); - } - self - } - -} - -// - -// impl< Former, Context, End > FormerBegin< Formed, Formed, Context > -// for VectorSubformer2< Former, Context, End > -// where -// End : FormingEnd2< VectorSubformerDescriptor< E >, Context >, -// // Formed : VectorLike2< E > + Default, -// Former : FormerDescriptor, -// { -// type End = End; -// -// #[ inline( always ) ] -// fn _begin -// ( -// formed : core::option::Option< Formed >, -// context : core::option::Option< Context >, -// on_end : End, -// ) -> Self -// { -// Self::begin( formed, context, on_end ) -// } -// -// } diff --git a/module/core/former/tests/inc/compiletime/former_bad_attr.stderr b/module/core/former/tests/inc/compiletime/former_bad_attr.stderr deleted file mode 100644 index bc5a44f0c1..0000000000 --- a/module/core/former/tests/inc/compiletime/former_bad_attr.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Unknown attribute #[defaultx(31)] - --> tests/inc/compiletime/former_bad_attr.rs:6:3 - | -6 | #[ defaultx( 31 ) ] - | ^^^^^^^^^^^^^^^^^^^ - -error: cannot find attribute `defaultx` in this scope - --> tests/inc/compiletime/former_bad_attr.rs:6:6 - | -6 | #[ defaultx( 31 ) ] - | ^^^^^^^^ help: a derive helper attribute with a similar name exists: `default` diff --git a/module/core/former/tests/inc/compiletime/components_component_from_debug.rs b/module/core/former/tests/inc/components_tests/compiletime/components_component_from_debug.rs similarity index 100% rename from module/core/former/tests/inc/compiletime/components_component_from_debug.rs rename to module/core/former/tests/inc/components_tests/compiletime/components_component_from_debug.rs diff --git a/module/core/former/tests/inc/components_tests/component_assign.rs b/module/core/former/tests/inc/components_tests/component_assign.rs index aa82903577..9ca13cbcba 100644 --- a/module/core/former/tests/inc/components_tests/component_assign.rs +++ b/module/core/former/tests/inc/components_tests/component_assign.rs @@ -14,4 +14,4 @@ struct Person // -include!( "../only_test/components_component_assign.rs" ); +include!( "./only_test/components_component_assign.rs" ); diff --git a/module/core/former/tests/inc/components_tests/component_assign_manual.rs b/module/core/former/tests/inc/components_tests/component_assign_manual.rs index b71e4f9624..d2aff86d2c 100644 --- a/module/core/former/tests/inc/components_tests/component_assign_manual.rs +++ b/module/core/former/tests/inc/components_tests/component_assign_manual.rs @@ -33,4 +33,4 @@ where // -include!( "../only_test/components_component_assign.rs" ); +include!( "./only_test/components_component_assign.rs" ); diff --git a/module/core/former/tests/inc/components_tests/component_from.rs b/module/core/former/tests/inc/components_tests/component_from.rs index e716895ff5..965218114f 100644 --- a/module/core/former/tests/inc/components_tests/component_from.rs +++ b/module/core/former/tests/inc/components_tests/component_from.rs @@ -17,4 +17,4 @@ pub struct Options1 // -include!( "../only_test/components_component_from.rs" ); +include!( "./only_test/components_component_from.rs" ); diff --git a/module/core/former/tests/inc/components_tests/component_from_manual.rs b/module/core/former/tests/inc/components_tests/component_from_manual.rs index 72912f2fb1..215a1f6ff5 100644 --- a/module/core/former/tests/inc/components_tests/component_from_manual.rs +++ b/module/core/former/tests/inc/components_tests/component_from_manual.rs @@ -42,4 +42,4 @@ impl From< &Options1 > for f32 // -include!( "../only_test/components_component_from.rs" ); +include!( "./only_test/components_component_from.rs" ); diff --git a/module/core/former/tests/inc/components_tests/components_assign.rs b/module/core/former/tests/inc/components_tests/components_assign.rs index 2f95f9fb5c..e2dbb6fda4 100644 --- a/module/core/former/tests/inc/components_tests/components_assign.rs +++ b/module/core/former/tests/inc/components_tests/components_assign.rs @@ -73,4 +73,4 @@ impl From< &Options2 > for String // -include!( "../only_test/components_components_assign.rs" ); +include!( "./only_test/components_components_assign.rs" ); diff --git a/module/core/former/tests/inc/components_tests/components_assign_manual.rs b/module/core/former/tests/inc/components_tests/components_assign_manual.rs index 11c499cd04..182ad0dacf 100644 --- a/module/core/former/tests/inc/components_tests/components_assign_manual.rs +++ b/module/core/former/tests/inc/components_tests/components_assign_manual.rs @@ -192,4 +192,4 @@ where // -include!( "../only_test/components_components_assign.rs" ); +include!( "./only_test/components_components_assign.rs" ); diff --git a/module/core/former/tests/inc/components_tests/composite.rs b/module/core/former/tests/inc/components_tests/composite.rs index 1fab721fe4..7105ba0b0e 100644 --- a/module/core/former/tests/inc/components_tests/composite.rs +++ b/module/core/former/tests/inc/components_tests/composite.rs @@ -72,4 +72,4 @@ pub struct Options2 // -include!( "../only_test/components_composite.rs" ); +include!( "./only_test/components_composite.rs" ); diff --git a/module/core/former/tests/inc/components_tests/composite_manual.rs b/module/core/former/tests/inc/components_tests/composite_manual.rs index 14ba81afe1..34e77f09af 100644 --- a/module/core/former/tests/inc/components_tests/composite_manual.rs +++ b/module/core/former/tests/inc/components_tests/composite_manual.rs @@ -209,4 +209,4 @@ where // -include!( "../only_test/components_composite.rs" ); +include!( "./only_test/components_composite.rs" ); diff --git a/module/core/former/tests/inc/components_tests/from_components.rs b/module/core/former/tests/inc/components_tests/from_components.rs index 6a2a61d125..9f69aab624 100644 --- a/module/core/former/tests/inc/components_tests/from_components.rs +++ b/module/core/former/tests/inc/components_tests/from_components.rs @@ -72,4 +72,4 @@ pub struct Options2 // -include!( "../only_test/components_from_components.rs" ); +include!( "./only_test/components_from_components.rs" ); diff --git a/module/core/former/tests/inc/components_tests/from_components_manual.rs b/module/core/former/tests/inc/components_tests/from_components_manual.rs index 3f01fe56cb..6a6c29e323 100644 --- a/module/core/former/tests/inc/components_tests/from_components_manual.rs +++ b/module/core/former/tests/inc/components_tests/from_components_manual.rs @@ -72,4 +72,4 @@ where // -include!( "../only_test/components_from_components.rs" ); +include!( "./only_test/components_from_components.rs" ); diff --git a/module/core/former/tests/inc/only_test/components_component_assign.rs b/module/core/former/tests/inc/components_tests/only_test/components_component_assign.rs similarity index 100% rename from module/core/former/tests/inc/only_test/components_component_assign.rs rename to module/core/former/tests/inc/components_tests/only_test/components_component_assign.rs diff --git a/module/core/former/tests/inc/only_test/components_component_from.rs b/module/core/former/tests/inc/components_tests/only_test/components_component_from.rs similarity index 100% rename from module/core/former/tests/inc/only_test/components_component_from.rs rename to module/core/former/tests/inc/components_tests/only_test/components_component_from.rs diff --git a/module/core/former/tests/inc/only_test/components_components_assign.rs b/module/core/former/tests/inc/components_tests/only_test/components_components_assign.rs similarity index 100% rename from module/core/former/tests/inc/only_test/components_components_assign.rs rename to module/core/former/tests/inc/components_tests/only_test/components_components_assign.rs diff --git a/module/core/former/tests/inc/only_test/components_composite.rs b/module/core/former/tests/inc/components_tests/only_test/components_composite.rs similarity index 100% rename from module/core/former/tests/inc/only_test/components_composite.rs rename to module/core/former/tests/inc/components_tests/only_test/components_composite.rs diff --git a/module/core/former/tests/inc/only_test/components_from_components.rs b/module/core/former/tests/inc/components_tests/only_test/components_from_components.rs similarity index 100% rename from module/core/former/tests/inc/only_test/components_from_components.rs rename to module/core/former/tests/inc/components_tests/only_test/components_from_components.rs diff --git a/module/core/former/tests/inc/former_tests/a_basic.rs b/module/core/former/tests/inc/former_tests/a_basic.rs new file mode 100644 index 0000000000..a3f7e74e5f --- /dev/null +++ b/module/core/former/tests/inc/former_tests/a_basic.rs @@ -0,0 +1,18 @@ +#![ deny( missing_docs ) ] + +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ derive( Debug, PartialEq, former::Former ) ] #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Struct1 +{ + pub int_1 : i32, +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/basic.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_basic_manual.rs b/module/core/former/tests/inc/former_tests/a_basic_manual.rs new file mode 100644 index 0000000000..4e0fd2aebc --- /dev/null +++ b/module/core/former/tests/inc/former_tests/a_basic_manual.rs @@ -0,0 +1,325 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq ) ] +pub struct Struct1 +{ + pub int_1 : i32, +} + +// == begin of generated + +// = formed + +#[ automatically_derived ] +impl Struct1 +{ + + #[ inline( always ) ] + pub fn former() -> Struct1Former< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > > + { + Struct1Former + ::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > > + ::new( former::ReturnPreformed ) + } + +} + +// = entity to former + +impl< Definition > former::EntityToFormer< Definition > for Struct1 +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, +{ + type Former = Struct1Former< Definition >; +} + +impl former::EntityToStorage for Struct1 +{ + type Storage = Struct1FormerStorage; +} + +impl< Context, Formed, End > former::EntityToDefinition< Context, Formed, End > +for Struct1 +where + End : former::FormingEnd< Struct1FormerDefinitionTypes< Context, Formed > >, +{ + type Definition = Struct1FormerDefinition< Context, Formed, End >; + type Types = Struct1FormerDefinitionTypes< Context, Formed >; +} + +impl< Context, Formed > former::EntityToDefinitionTypes< Context, Formed > +for Struct1 +{ + type Types = Struct1FormerDefinitionTypes< Context, Formed >; +} + +// = definition types + +#[ derive( Debug ) ] +// pub struct Struct1FormerDefinitionTypes< Context = (), Formed = Struct1 > +pub struct Struct1FormerDefinitionTypes< Context, Formed > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} + +impl< Context, Formed > Default for Struct1FormerDefinitionTypes< Context, Formed > +{ + fn default() -> Self + { + Self { _phantom : core::marker::PhantomData, } + } +} + +impl< Context, Formed > former::FormerDefinitionTypes +for Struct1FormerDefinitionTypes< Context, Formed > +{ + type Storage = Struct1FormerStorage; + type Formed = Formed; + type Context = Context; +} + +// = definition + +#[ derive( Debug ) ] +// pub struct Struct1FormerDefinition< Context = (), Formed = Struct1, End = former::ReturnPreformed > +pub struct Struct1FormerDefinition< Context, Formed, End > +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} + +impl< Context, Formed, End > Default for Struct1FormerDefinition< Context, Formed, End > +{ + fn default() -> Self + { + Self { _phantom : core::marker::PhantomData, } + } +} + +impl< Context, Formed, End > former::FormerDefinition for Struct1FormerDefinition< Context, Formed, End > +where + End : former::FormingEnd< Struct1FormerDefinitionTypes< Context, Formed > > +{ + type Storage = Struct1FormerStorage; + type Formed = Formed; + type Context = Context; + type Types = Struct1FormerDefinitionTypes< Context, Formed >; + type End = End; +} + +// pub type Struct1FormerWithClosure< Context, Formed > = +// Struct1FormerDefinition< Context, Formed, former::FormingEndClosure< Struct1FormerDefinitionTypes< Context, Formed > > >; + +// = storage + +pub struct Struct1FormerStorage +{ + pub int_1 : ::core::option::Option< i32 >, +} + +impl ::core::default::Default for Struct1FormerStorage +{ + #[ inline( always ) ] + fn default() -> Self + { + Self { int_1 : ::core::option::Option::None, } + } +} + +impl former::Storage for Struct1FormerStorage +{ + type Preformed = Struct1; +} + +impl former::StoragePreform for Struct1FormerStorage +{ + // type Preformed = < Self as former::Storage >::Formed; + fn preform( mut self ) -> Self::Preformed + { + let int_1 = if self.int_1.is_some() + { + self.int_1.take().unwrap() + } + else + { + { + trait MaybeDefault< T > + { + fn maybe_default( self : & Self ) -> T + { + panic!( "Field 'int_1' isn't initialized" ) + } + } + + impl< T > MaybeDefault< T > for & ::core::marker::PhantomData< T > {} + impl< T > MaybeDefault< T > for ::core::marker::PhantomData< T > + where T : ::core::default::Default, + { + fn maybe_default( self : & Self ) -> T { T::default() } + } + + (& ::core::marker::PhantomData::< i32 >).maybe_default() + } + }; + let result = Struct1 { int_1, }; + return result; + } +} + +// = former mutator + +impl< Context, Formed > former::FormerMutator +for Struct1FormerDefinitionTypes< Context, Formed > +{ +} + +// = former + +pub struct Struct1Former +< + Definition = Struct1FormerDefinition< (), Struct1, former::ReturnPreformed >, +> +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, +{ + storage : Definition::Storage, + context : ::core::option::Option< Definition::Context >, + on_end : ::core::option::Option< Definition::End >, +} + +#[ automatically_derived ] +impl< Definition > Struct1Former< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, + +{ + + #[ inline( always ) ] + pub fn perform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + let result = self.form(); + return result; + } + + #[ inline( always ) ] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin_coercing( None, None, on_end ) + } + + #[ inline( always ) ] + pub fn new_coercing< IntoEnd >( end : IntoEnd ) -> Self + where IntoEnd : Into< Definition::End >, + { + Self::begin_coercing( None, None, end, ) + } + + #[ inline( always ) ] + pub fn begin + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : < Definition as former::FormerDefinition >::End, + ) + -> Self + { + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some( on_end ), + } + } + + #[ inline( always ) ] + pub fn begin_coercing< IntoEnd > + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : IntoEnd, + ) + -> Self + where + IntoEnd : ::core::convert::Into< < Definition as former::FormerDefinition >::End >, + { + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some( ::core::convert::Into::into( on_end ) ), + } + } + + #[ inline( always ) ] + pub fn form( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + self.end() + } + + #[ inline( always ) ] + pub fn end( mut self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let mut context = self.context.take(); + < Definition::Types as former::FormerMutator >::form_mutation( &mut self.storage, &mut context ); + former::FormingEnd::< Definition::Types >::call( & on_end, self.storage, context ) + } + + #[ inline ] + pub fn int_1< Src >( mut self, src : Src ) -> Self + where Src : ::core::convert::Into< i32 >, + { + debug_assert!( self.storage.int_1.is_none() ); + self.storage.int_1 = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + self + } + +} + +// = preform with Storage::preform + +impl< Definition > Struct1Former< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage, Formed = Struct1 >, + Definition::Storage : former::StoragePreform< Preformed = Struct1 >, + +{ + pub fn preform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + former::StoragePreform::preform( self.storage ) + } +} + +impl< Definition > former::FormerBegin< Definition > +for Struct1Former< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, + +{ + + #[ inline( always ) ] + fn former_begin + ( + storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : Definition::End, + ) + -> Self + { + debug_assert!( storage.is_none() ); + Self::begin( None, context, on_end ) + } + +} + +// == end of generated + +include!( "./only_test/basic.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_containers.rs b/module/core/former/tests/inc/former_tests/a_containers.rs new file mode 100644 index 0000000000..c2466841bf --- /dev/null +++ b/module/core/former/tests/inc/former_tests/a_containers.rs @@ -0,0 +1,26 @@ +#![ deny( missing_docs ) ] + +#[ allow( unused_imports ) ] +use super::*; + +// use std::collections::HashMap; +// use std::collections::HashSet; + +#[ derive( Default, Debug, PartialEq, former::Former ) ] +// #[ derive( Default, Debug, PartialEq, former::Former ) ] #[ debug ] +// #[ derive( Default, Debug, PartialEq ) ] +pub struct Struct1 +{ + #[ container( definition = former::VectorDefinition ) ] + vec_1 : Vec< String >, + #[ container( definition = former::HashMapDefinition ) ] + hashmap_1 : std::collections::HashMap< String, String >, + #[ container( definition = former::HashSetDefinition ) ] + hashset_1 : std::collections::HashSet< String >, +} + +// == generated begin + +// == generated end + +include!( "./only_test/containers_with_subformer.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_containers_manual.rs b/module/core/former/tests/inc/former_tests/a_containers_manual.rs new file mode 100644 index 0000000000..eed2cbfa9a --- /dev/null +++ b/module/core/former/tests/inc/former_tests/a_containers_manual.rs @@ -0,0 +1,672 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Default, Debug, PartialEq ) ] +pub struct Struct1 +{ + vec_1 : Vec< String >, + hashmap_1 : collection_tools::HashMap< String, String >, + hashset_1 : collection_tools::HashSet< String >, +} + +// == begin of generated + +#[automatically_derived] +impl< > Struct1< > +where +{ + + + + #[ inline( always ) ] + pub fn former() -> Struct1Former< + Struct1FormerDefinition<(), Struct1<>, former::ReturnPreformed> + > + { + Struct1Former::< Struct1FormerDefinition< (), Struct1<>, former::ReturnPreformed > >::new_coercing(former::ReturnPreformed) + } +} + +impl< Definition > former::EntityToFormer< Definition > +for Struct1< > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage<> >, +{ + type Former = Struct1Former< Definition >; +} + +impl< > former::EntityToStorage for Struct1< > +where +{ + type Storage = Struct1FormerStorage<>; +} + +#[derive(Debug)] +pub struct Struct1FormerDefinitionTypes< Context = (), Formed = Struct1<>, > +where +{ + _phantom : core::marker::PhantomData<(Context, Formed)>, +} + +impl< Context, Formed, > core::default::Default +for Struct1FormerDefinitionTypes< Context, Formed, > +where +{ + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +impl< Context, Formed, > former::FormerDefinitionTypes +for Struct1FormerDefinitionTypes< Context, Formed, > +where +{ + type Storage = Struct1FormerStorage<>; + type Formed = Formed; + type Context = Context; +} + +impl< Context, Formed > former::FormerMutator +for Struct1FormerDefinitionTypes< Context, Formed > +{ +} + +#[derive(Debug)] +pub struct Struct1FormerDefinition< Context = (), Formed = Struct1<>, End = former::ReturnPreformed, > +where +{ + _phantom : core::marker::PhantomData<(Context, Formed, End)>, +} + +impl< Context, Formed, End, > core::default::Default for Struct1FormerDefinition< Context, Formed, End, > +where +{ + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +impl< Context, Formed, End, > former::FormerDefinition for Struct1FormerDefinition< Context, Formed, End, > +where + End : former::FormingEnd< Struct1FormerDefinitionTypes< Context, Formed, > >, +{ + type Types = Struct1FormerDefinitionTypes< Context, Formed, >; + type End = End; + type Storage = Struct1FormerStorage<>; + type Formed = Formed; + type Context = Context; +} + + +pub struct Struct1FormerStorage<> +where +{ + + pub vec_1 : core::option::Option>, + + pub hashmap_1 : core::option::Option>, + + pub hashset_1 : core::option::Option>, +} + +impl< > core::default::Default for Struct1FormerStorage<> +where +{ + #[ inline( always ) ] + fn default() -> Self + { + Self + { + vec_1 : core::option::Option::None, + hashmap_1 : core::option::Option::None, + hashset_1 : core::option::Option::None, + } + } +} + +impl< > former::Storage for Struct1FormerStorage<> +where +{ + type Preformed = Struct1<>; +} + +impl< > former::StoragePreform for Struct1FormerStorage<> +where +{ + // type Preformed = Struct1<>; + + fn preform(mut self) -> Self::Preformed + { + let vec_1 = if self.vec_1.is_some() + { + self.vec_1.take().unwrap() + } + else + { + { + trait MaybeDefault + { + fn maybe_default(self: &Self) -> T + { + panic!("Field 'vec_1' isn't initialized") + } + } + + impl MaybeDefault for &core::marker::PhantomData {} + + impl MaybeDefault for core::marker::PhantomData + where + T : core::default::Default, + { + fn maybe_default(self: &Self) -> T + { + T::default() + } + } + + (&core::marker::PhantomData::>).maybe_default() + } + }; + + let hashmap_1 = if self.hashmap_1.is_some() + { + self.hashmap_1.take().unwrap() + } + else + { + { + trait MaybeDefault + { + fn maybe_default(self: &Self) -> T + { + panic!("Field 'hashmap_1' isn't initialized") + } + } + + impl MaybeDefault for &core::marker::PhantomData {} + + impl MaybeDefault for core::marker::PhantomData + where + T : core::default::Default, + { + fn maybe_default(self: &Self) -> T + { + T::default() + } + } + + (&core::marker::PhantomData::>).maybe_default() + } + }; + + let hashset_1 = if self.hashset_1.is_some() + { + self.hashset_1.take().unwrap() + } + else + { + { + trait MaybeDefault + { + fn maybe_default(self: &Self) -> T + { + panic!("Field 'hashset_1' isn't initialized") + } + } + + impl MaybeDefault for &core::marker::PhantomData {} + + impl MaybeDefault for core::marker::PhantomData + where + T : core::default::Default, + { + fn maybe_default(self: &Self) -> T + { + T::default() + } + } + + (&core::marker::PhantomData::>).maybe_default() + } + }; + + let result = Struct1::<> + { + vec_1, hashmap_1, hashset_1, + }; + + return result; + } +} + +pub struct Struct1Former< Definition = Struct1FormerDefinition<(), Struct1<>, former::ReturnPreformed>, > +where + Definition : former::FormerDefinition, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage<> >, +{ + storage : ::Storage, + context : core::option::Option<::Context>, + on_end : core::option::Option, +} + +#[automatically_derived] +impl< Definition, > Struct1Former< Definition, > +where + Definition : former::FormerDefinition, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage<> >, +{ + + + + #[ inline( always ) ] + pub fn new(on_end: Definition::End) -> Self + { + Self::begin_coercing(None, None, on_end) + } + + + + + #[ inline( always ) ] + pub fn new_coercing(end: IntoEnd) -> Self + where + IntoEnd : Into, + { + Self::begin_coercing(None, None, end,) + } + + + + + #[ inline( always ) ] + pub fn begin(mut storage: core::option::Option<::Storage>, context: core::option::Option<::Context>, on_end: ::End,) -> Self + { + if storage.is_none() + { + storage = Some(core::default::Default::default()); + } + Self + { + storage: storage.unwrap(), + context: context, + on_end: core::option::Option::Some(on_end), + } + } + + + + + #[ inline( always ) ] + pub fn begin_coercing(mut storage: core::option::Option<::Storage>, context: core::option::Option<::Context>, on_end: IntoEnd,) -> Self + where + IntoEnd : core::convert::Into<::End>, + { + if storage.is_none() + { + storage = Some(core::default::Default::default()); + } + Self + { + storage: storage.unwrap(), + context: context, + on_end: core::option::Option::Some(core::convert::Into::into(on_end)), + } + } + + + + + #[ inline( always ) ] + pub fn form(self) -> ::Formed + { + self.end() + } + + #[ inline( always ) ] + pub fn end(mut self) -> ::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + former::FormingEnd::::call(&on_end, self.storage, context) + } + + #[ inline( always ) ] + pub fn _vec_1_assign< Former2 >( self ) -> Former2 + where + Former2 : former::FormerBegin + < + former::VectorDefinition< String, Self, Self, Struct1FormerAssignVec1End< Definition > >, + >, + former::VectorDefinition< String, Self, Self, Struct1FormerAssignVec1End< Definition > > : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < collection_tools::Vec< String > as former::Container >::Entry >, + Storage = Vec< String >, + Context = Struct1Former< Definition >, + End = Struct1FormerAssignVec1End< Definition >, + >, + Struct1FormerAssignVec1End< Definition > : former::FormingEnd + < + < collection_tools::Vec< String > as former::EntityToDefinitionTypes< Self, Self > >::Types + >, + { + Former2::former_begin( None, Some( self ), Struct1FormerAssignVec1End::< Definition >::default() ) + } + + #[ inline( always ) ] + pub fn vec_1( self ) -> former::ContainerFormer:: + < + String, + former::VectorDefinition< String, Self, Self, Struct1FormerAssignVec1End< Definition > >, + > + where + former::VectorDefinition< String, Self, Self, Struct1FormerAssignVec1End< Definition > > : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < collection_tools::Vec< String > as former::Container >::Entry >, + Storage = Vec< String >, + Context = Struct1Former< Definition >, + End = Struct1FormerAssignVec1End< Definition >, + >, + Struct1FormerAssignVec1End< Definition > : former::FormingEnd + < + < collection_tools::Vec< String > as former::EntityToDefinitionTypes< Self, Self > >::Types + >, + { + self._vec_1_assign::< former::ContainerFormer:: + < + String, + former::VectorDefinition< String, Self, Self, Struct1FormerAssignVec1End< Definition > >, + > > () + } + + #[ inline( always ) ] + pub fn _hashmap_1_assign< Former2 >( self ) -> Former2 + where + Former2 : former::FormerBegin + < + former::HashMapDefinition< String, String, Self, Self, Struct1FormerAssignHashmap1End< Definition > >, + >, + former::HashMapDefinition< String, String, Self, Self, Struct1FormerAssignHashmap1End< Definition > > : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < collection_tools::HashMap< String, String > as former::Container >::Entry >, + Storage = collection_tools::HashMap< String, String >, + Context = Struct1Former< Definition >, + End = Struct1FormerAssignHashmap1End< Definition >, + >, + Struct1FormerAssignHashmap1End< Definition > : former::FormingEnd + < + < collection_tools::HashMap< String, String > as former::EntityToDefinitionTypes< Self, Self > >::Types + >, + { + Former2::former_begin( None, Some( self ), Struct1FormerAssignHashmap1End::< Definition >::default() ) + } + + #[ inline( always ) ] + pub fn hashmap_1( self ) -> former::ContainerFormer:: + < + ( String, String ), + former::HashMapDefinition< String, String, Self, Self, Struct1FormerAssignHashmap1End< Definition > >, + > + where + former::HashMapDefinition< String, String, Self, Self, Struct1FormerAssignHashmap1End< Definition > > : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < collection_tools::HashMap< String, String > as former::Container >::Entry >, + Storage = collection_tools::HashMap< String, String >, + Context = Struct1Former< Definition >, + End = Struct1FormerAssignHashmap1End< Definition >, + >, + Struct1FormerAssignHashmap1End< Definition > : former::FormingEnd + < + < collection_tools::HashMap< String, String > as former::EntityToDefinitionTypes< Self, Self > >::Types + >, + { + self._hashmap_1_assign::< former::ContainerFormer:: + < + ( String, String ), + former::HashMapDefinition< String, String, Self, Self, Struct1FormerAssignHashmap1End< Definition > >, + > > () + } + + #[ inline( always ) ] + pub fn _hashset_1_assign< Former2 >( self ) -> Former2 + where + Former2 : former::FormerBegin + < + former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > >, + >, + former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > > : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < collection_tools::HashSet< String > as former::Container >::Entry >, + Storage = collection_tools::HashSet< String >, + Context = Struct1Former< Definition >, + End = Struct1FormerAssignHashset1End< Definition >, + >, + Struct1FormerAssignHashset1End< Definition > : former::FormingEnd + < + < collection_tools::HashSet< String > as former::EntityToDefinitionTypes< Self, Self > >::Types + >, + { + Former2::former_begin( None, Some( self ), Struct1FormerAssignHashset1End::< Definition >::default() ) + } + + #[ inline( always ) ] + pub fn hashset_1( self ) -> former::ContainerFormer:: + < + String, + former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > >, + > + where + former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > > : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < collection_tools::HashSet< String > as former::Container >::Entry >, + Storage = collection_tools::HashSet< String >, + Context = Struct1Former< Definition >, + End = Struct1FormerAssignHashset1End< Definition >, + >, + Struct1FormerAssignHashset1End< Definition > : former::FormingEnd + < + < collection_tools::HashSet< String > as former::EntityToDefinitionTypes< Self, Self > >::Types + >, + { + self._hashset_1_assign::< former::ContainerFormer:: + < + String, + former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > >, + > > () + } + +} + +impl< Definition, > Struct1Former< Definition, > +where + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage<>, Formed = Struct1<> >, + Definition : former::FormerDefinition, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage<> >, +{ + pub fn preform(self) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + former::StoragePreform::preform(self.storage) + } +} + +impl< Definition, > Struct1Former< Definition, > +where + Definition : former::FormerDefinition, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage<>, Formed = Struct1<> >, +{ + + #[ inline( always ) ] + pub fn perform(self) -> ::Formed + { + let result = self.form(); + return result; + } +} + +impl< Definition > former::FormerBegin< Definition > for Struct1Former< Definition, > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage<> >, +{ + #[ inline( always ) ] + fn former_begin(storage: core::option::Option, context: core::option::Option, on_end: Definition::End,) -> Self + { + debug_assert!(storage.is_none()); + Self::begin(None, context, on_end) + } +} + +#[ allow( dead_code ) ] +pub type Struct1AsSubformer< Superformer, End > = Struct1Former +< + Struct1FormerDefinition< Superformer, Superformer, End, >, +>; + +#[ allow( dead_code ) ] +pub trait Struct1AsSubformerEnd +where Self : former::FormingEnd< Struct1FormerDefinitionTypes, > +{} + +impl Struct1AsSubformerEnd for T +where + Self : former::FormingEnd< Struct1FormerDefinitionTypes, >, +{} + +// = former assign end + +pub struct Struct1FormerAssignVec1End< Definition > +{ + _phantom : core::marker::PhantomData< ( Definition, ) >, +} + +impl Default for Struct1FormerAssignVec1End< Definition > +{ + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +// Struct1Former< Definition = Struct1FormerDefinition<(), Struct1<>, former::ReturnPreformed>, > + +impl< Definition > former::FormingEnd +< + former::VectorDefinitionTypes< String, Struct1Former< Definition >, Struct1Former< Definition > > +> +for Struct1FormerAssignVec1End< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage >, +{ + #[ inline( always ) ] + fn call( &self, storage : collection_tools::Vec< String >, super_former : Option< Struct1Former< Definition > > ) + -> Struct1Former< Definition, > + { + let mut super_former = super_former.unwrap(); + if let Some( ref mut field ) = super_former.storage.vec_1 + { + former::ContainerAssign::assign( field, storage ); + } + else + { + super_former.storage.vec_1 = Some( storage ); + } + super_former + } +} + +pub struct Struct1FormerAssignHashmap1End +{ + _phantom : core::marker::PhantomData<(Definition,)>, +} + +impl Default for Struct1FormerAssignHashmap1End +{ + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +impl< Definition, > former::FormingEnd +< former::HashMapDefinitionTypes< String, String, Struct1Former< Definition >, Struct1Former< Definition > > > +for Struct1FormerAssignHashmap1End< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage >, +{ + #[ inline( always ) ] + fn call( &self, storage : collection_tools::HashMap< String, String >, super_former : Option< Struct1Former< Definition > > ) + -> Struct1Former< Definition, > + { + let mut super_former = super_former.unwrap(); + if let Some( ref mut field ) = super_former.storage.hashmap_1 + { + former::ContainerAssign::assign( field, storage ); + } + else + { + super_former.storage.hashmap_1 = Some( storage ); + } + super_former + } +} + +pub struct Struct1FormerAssignHashset1End +{ + _phantom : core::marker::PhantomData<(Definition,)>, +} + +impl Default for Struct1FormerAssignHashset1End +{ + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +impl< Definition, > former::FormingEnd +< former::HashSetDefinitionTypes< String, Struct1Former< Definition >, Struct1Former< Definition > > > +for Struct1FormerAssignHashset1End< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage >, +{ + #[ inline( always ) ] + fn call( &self, storage : collection_tools::HashSet< String >, super_former : Option< Struct1Former< Definition >, > ) + -> Struct1Former< Definition, > + { + let mut super_former = super_former.unwrap(); + if let Some( ref mut field ) = super_former.storage.hashset_1 + { + former::ContainerAssign::assign( field, storage ); + } + else + { + super_former.storage.hashset_1 = Some( storage ); + } + super_former + } +} + +// == end of generated + +include!( "./only_test/containers_with_subformer.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_containers_scalar.rs b/module/core/former/tests/inc/former_tests/a_containers_scalar.rs new file mode 100644 index 0000000000..4b3efafb88 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/a_containers_scalar.rs @@ -0,0 +1,23 @@ +#![ deny( missing_docs ) ] + +#[ allow( unused_imports ) ] +use super::*; + +use std::collections::HashMap; +use std::collections::HashSet; + +#[ derive( Debug, PartialEq, the_module::Former ) ] +// #[ derive( Debug, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Struct1 +{ + vec_1 : Vec< String >, + hashmap_1 : HashMap< String, String >, + hashset_1 : HashSet< String >, +} + +// = begin_coercing of generated + +// == end of generated + +include!( "./only_test/containers_without_subformer.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_containers_with_runtime.rs b/module/core/former/tests/inc/former_tests/a_containers_with_runtime.rs deleted file mode 100644 index ead3284c94..0000000000 --- a/module/core/former/tests/inc/former_tests/a_containers_with_runtime.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -// use std::collections::HashMap; -// use std::collections::HashSet; - -#[ derive( Debug, PartialEq, the_module::Former ) ] -pub struct Struct1 -{ - #[ subformer( the_module::VectorSubformer ) ] - vec_1 : Vec< String >, - #[ subformer( the_module::HashMapSubformer ) ] - hashmap_strings_1 : std::collections::HashMap< String, String >, - #[ subformer( the_module::HashSetSubformer ) ] - hashset_strings_1 : std::collections::HashSet< String >, -} - -include!( "../only_test/containers_with_runtime.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_containers_with_runtime_manual.rs b/module/core/former/tests/inc/former_tests/a_containers_with_runtime_manual.rs deleted file mode 100644 index 87a3bec1eb..0000000000 --- a/module/core/former/tests/inc/former_tests/a_containers_with_runtime_manual.rs +++ /dev/null @@ -1,295 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -#[ derive( Debug, PartialEq ) ] -pub struct Struct1 -{ - vec_1 : Vec< String >, - hashmap_strings_1 : std::collections::HashMap< String, String >, - hashset_strings_1 : std::collections::HashSet< String >, -} - -// - -impl Struct1 -{ - pub fn former() -> Struct1Former< Struct1, the_module::ReturnFormed > - { - Struct1Former::< Struct1, the_module::ReturnFormed >::new() - } -} - -// generated by former -pub struct Struct1FormerStorage -{ - pub vec_1 : ::core::option::Option< Vec< String > >, - pub hashmap_strings_1 : ::core::option::Option< std::collections::HashMap< String, String > >, - pub hashset_strings_1 : ::core::option::Option< std::collections::HashSet< String > >, -} - -impl Default for Struct1FormerStorage -{ - - #[ inline( always ) ] - fn default() -> Self - { - Self - { - vec_1 : None, - hashmap_strings_1 : None, - hashset_strings_1 : None, - } - } - -} - -// - -pub struct Struct1Former -< - Context = Struct1, - End = the_module::ReturnFormed, -> -where - End : the_module::FormingEnd< Struct1, Context >, -{ - storage : Struct1FormerStorage, - context : ::core::option::Option< Context >, - on_end : ::core::option::Option< End >, -} - -impl< Context, End > Struct1Former< Context, End > -where - End : the_module::FormingEnd< Struct1, Context >, -{ - - #[ inline( always ) ] - fn form( mut self ) -> Struct1 - { - - let vec_1 = if self.storage.vec_1.is_some() - { - self.storage.vec_1.take().unwrap() - } - else - { - let val : Vec< String > = Default::default(); - val - }; - - let hashmap_strings_1 = if self.storage.hashmap_strings_1.is_some() - { - self.storage.hashmap_strings_1.take().unwrap() - } - else - { - let val : std::collections::HashMap< String, String > = Default::default(); - val - }; - - let hashset_strings_1 = if self.storage.hashset_strings_1.is_some() - { - self.storage.hashset_strings_1.take().unwrap() - } - else - { - let val : std::collections::HashSet< String > = Default::default(); - val - }; - - Struct1 - { - vec_1, - hashmap_strings_1, - hashset_strings_1, - } - - } - - #[ inline( always ) ] - pub fn perform(self) -> Struct1 - { - let result = self.form(); - return result; - } - - // #[ inline( always ) ] - // pub fn new() -> Struct1Former - // { - // Struct1Former:: - // < - // Struct1, - // the_module::ReturnFormed, - // >::begin(None, the_module::ReturnFormed) - // } - - #[ inline( always ) ] - pub fn begin - ( - mut storage : ::core::option::Option< Struct1FormerStorage >, - context : ::core::option::Option< Context >, - on_end : End, - ) -> Self - { - if storage.is_none() - { - storage = Some( Default::default() ); - } - Self - { - storage : storage.unwrap(), - context, - on_end : ::core::option::Option::Some( on_end ), - } - } - - #[ inline( always ) ] - pub fn end( mut self ) -> Context - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - #[ inline( always ) ] - pub fn __vec_1< Former2 >( self ) -> - Former2 - where - Former2 : former::FormerBegin - < - Vec< String >, - Vec< String >, - Self, End = former::FormingEndWrapper< Vec< String >, Self >, - >, - { - let on_end = | formed : Vec< String >, super_former : ::core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if let Some( ref mut field ) = super_former.storage.vec_1 - { - former::ContainerAssign::assign( field, formed ); - } - else - { - super_former.storage.vec_1 = Some( formed ); - } - super_former - }; - Former2::_begin( None, Some( self ), former::FormingEndWrapper::new( on_end ) ) - } - - // xxx2 : continue - pub fn vec_1( self ) -> the_module::VectorSubformer - < - String, - Vec< String >, - Self, - impl the_module::FormingEnd< Vec< String >, Self >, - > - { - self.__vec_1::< the_module::VectorSubformer::< _, _, _, _ > >() - } - - // pub fn vec_1( mut self ) -> the_module::VectorSubformer - // < - // String, - // Vec< String >, - // Self, - // impl the_module::FormingEnd< Vec< String >, Self >, - // > - // { - // let formed = self.storage.vec_1.take(); - // let on_end = | formed : Vec< String >, super_former : ::core::option::Option< Self > | -> Self - // { - // let mut super_former = super_former.unwrap(); - // super_former.storage.vec_1 = Some( formed ); - // super_former - // }; - // the_module::VectorSubformer::< String, Vec< String >, Self, _ >::begin( Some( self ), formed, on_end ) - // } - - pub fn hashmap_strings_1( mut self ) -> the_module::HashMapSubformer - < - String, - String, - std::collections::HashMap< String, String >, - Self, - impl the_module::FormingEnd< std::collections::HashMap< String, String >, Self >, - > - { - let formed = self.storage.hashmap_strings_1.take(); - let on_end = | formed : std::collections::HashMap< String, String >, super_former : ::core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - super_former.storage.hashmap_strings_1 = Some( formed ); - super_former - }; - the_module::HashMapSubformer::begin( formed, Some( self ), on_end ) - } - - pub fn hashset_strings_1( mut self ) -> the_module::HashSetSubformer - < - String, - std::collections::HashSet< String >, - Self, - impl the_module::FormingEnd< std::collections::HashSet< String >, Self >, - > - { - let formed = self.storage.hashset_strings_1.take(); - let on_end = | formed : std::collections::HashSet< String >, super_former : ::core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - super_former.storage.hashset_strings_1 = Some( formed ); - super_former - }; - the_module::HashSetSubformer::begin( formed, Some( self ), on_end ) - } - -} - -// impl< Context, End > Struct1Former< Context, End > -// where -// End: the_module::FormingEnd, - -impl Struct1Former< Struct1, the_module::ReturnFormed > -{ - - #[ inline( always ) ] - pub fn new() -> Self - { - Self::begin( None, None, the_module::ReturnFormed ) - } - -} - -// - -// impl< Context, End > Struct1Former< Context, End > -// where -// End : the_module::FormingEnd< Struct1, Context >, - -impl< Context, End > former::FormerBegin< Struct1FormerStorage, Struct1, Context > -for Struct1Former< Context, End > -where - End : the_module::FormingEnd< Struct1, Context >, -{ - type End = End; - - #[ inline( always ) ] - fn _begin - ( - storage : core::option::Option< Struct1FormerStorage >, /* xxx2 : that should be storage */ - context : core::option::Option< Context >, - on_end : End, - ) -> Self - { - debug_assert!( storage.is_none() ); - Self::begin( None, context, on_end ) - } - -} - -// - -include!( "../only_test/containers_with_runtime.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_containers_without_runtime.rs b/module/core/former/tests/inc/former_tests/a_containers_without_runtime.rs deleted file mode 100644 index 26b9e6ef34..0000000000 --- a/module/core/former/tests/inc/former_tests/a_containers_without_runtime.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -use std::collections::HashMap; -use std::collections::HashSet; - -#[ derive( Debug, PartialEq, the_module::Former ) ] -// #[ debug ] -pub struct Struct1 -{ - vec_1 : Vec< String >, - hashmap_strings_1 : HashMap< String, String >, - hashset_strings_1 : HashSet< String >, -} - -// - -include!( "../only_test/containers_without_runtime.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_containers_without_runtime_manual.rs b/module/core/former/tests/inc/former_tests/a_containers_without_runtime_manual.rs deleted file mode 100644 index 9bea46cff7..0000000000 --- a/module/core/former/tests/inc/former_tests/a_containers_without_runtime_manual.rs +++ /dev/null @@ -1,178 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -#[ derive( Debug, PartialEq ) ] -pub struct Struct1 -{ - vec_1 : Vec< String >, - hashmap_strings_1 : std::collections::HashMap< String, String >, - hashset_strings_1 : std::collections::HashSet< String >, -} - -// - -impl Struct1 -{ - pub fn former() -> Struct1Former< Struct1, the_module::ReturnFormed > - { - Struct1Former::< Struct1, the_module::ReturnFormed >::new() - } -} - -// generated by former -pub struct Struct1FormerStorage -{ - pub vec_1 : core::option::Option< Vec< String > >, - pub hashmap_strings_1 : core::option::Option< std::collections::HashMap< String, String > >, - pub hashset_strings_1 : core::option::Option< std::collections::HashSet< String > >, -} - -impl Default for Struct1FormerStorage -{ - - #[ inline( always ) ] - fn default() -> Self - { - Self - { - vec_1 : None, - hashmap_strings_1 : None, - hashset_strings_1 : None, - } - } - -} - -// - -pub struct Struct1Former -< - __FormerContext = Struct1, - __FormerEnd = the_module::ReturnFormed, -> -where - __FormerEnd : the_module::FormingEnd< Struct1, __FormerContext >, -{ - storage : Struct1FormerStorage, - context : core::option::Option< __FormerContext >, - on_end : core::option::Option< __FormerEnd >, -} - -impl< __FormerContext, __FormerEnd > Struct1Former< __FormerContext, __FormerEnd > -where - __FormerEnd: the_module::FormingEnd, -{ - - #[ inline( always ) ] - fn form( mut self ) -> Struct1 - { - - let vec_1 = if self.storage.vec_1.is_some() - { - self.storage.vec_1.take().unwrap() - } - else - { - let val : Vec< String > = Default::default(); - val - }; - - let hashmap_strings_1 = if self.storage.hashmap_strings_1.is_some() - { - self.storage.hashmap_strings_1.take().unwrap() - } - else - { - let val : std::collections::HashMap< String, String > = Default::default(); - val - }; - - let hashset_strings_1 = if self.storage.hashset_strings_1.is_some() - { - self.storage.hashset_strings_1.take().unwrap() - } - else - { - let val : std::collections::HashSet< String > = Default::default(); - val - }; - - Struct1 - { - vec_1, - hashmap_strings_1, - hashset_strings_1, - } - - } - - #[ inline( always ) ] - pub fn perform(self) -> Struct1 - { - let result = self.form(); - return result; - } - - #[ inline( always ) ] - pub fn new() -> Struct1Former - { - Struct1Former:: - < - Struct1, - the_module::ReturnFormed, - >::begin(None, the_module::ReturnFormed) - } - - #[ inline( always ) ] - pub fn begin - ( - context : core::option::Option< __FormerContext >, - on_end : __FormerEnd, - ) -> Self - { - Self - { - storage : core::default::Default::default(), - context : context, - on_end : ::core::option::Option::Some( on_end ), - } - } - - #[ inline( always ) ] - pub fn end( mut self ) -> __FormerContext - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - pub fn vec_1< Src >( mut self, src : Src ) -> Self - where Src : core::convert::Into< Vec< String > > - { - debug_assert!( self.storage.vec_1.is_none() ); - self.storage.vec_1 = Some( src.into() ); - self - } - - pub fn hashmap_strings_1< Src >( mut self, src : Src ) -> Self - where Src : core::convert::Into< std::collections::HashMap< String, String > > - { - debug_assert!( self.storage.hashmap_strings_1.is_none() ); - self.storage.hashmap_strings_1 = Some( src.into() ); - self - } - - pub fn hashset_strings_1< Src >( mut self, src : Src ) -> Self - where Src : core::convert::Into< std::collections::HashSet< String > > - { - debug_assert!( self.storage.hashset_strings_1.is_none() ); - self.storage.hashset_strings_1 = Some( src.into() ); - self - } - -} - -// - -include!( "../only_test/containers_without_runtime.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_primitives.rs b/module/core/former/tests/inc/former_tests/a_primitives.rs new file mode 100644 index 0000000000..658420597c --- /dev/null +++ b/module/core/former/tests/inc/former_tests/a_primitives.rs @@ -0,0 +1,21 @@ +#![ deny( missing_docs ) ] + +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ derive( Debug, PartialEq, former::Former ) ] #[ debug ] +// #[ derive( Debug, PartialEq ) ] #[ debug ] +pub struct Struct1 +{ + pub int_1 : i32, + string_1 : String, + int_optional_1 : core::option::Option< i32 >, + string_optional_1 : Option< String >, +} + +// = begin_coercing of generated + +// == end of generated + +include!( "./only_test/primitives.rs" ); diff --git a/module/core/former/tests/inc/former_tests/a_primitives_manual.rs b/module/core/former/tests/inc/former_tests/a_primitives_manual.rs index 0c5a011178..90e1290d6b 100644 --- a/module/core/former/tests/inc/former_tests/a_primitives_manual.rs +++ b/module/core/former/tests/inc/former_tests/a_primitives_manual.rs @@ -10,18 +10,81 @@ pub struct Struct1 string_optional_1 : Option< String >, } -// +// = formed // generated by former impl Struct1 { - pub fn former() -> Struct1Former< Struct1, the_module::ReturnFormed > + pub fn former() -> Struct1Former { - Struct1Former::< Struct1, the_module::ReturnFormed >::new() + Struct1Former::new_coercing( former::ReturnPreformed ) } } -// +// = definition + +#[ derive( Debug ) ] +pub struct Struct1FormerDefinition< Context = (), Formed = Struct1, End = former::ReturnPreformed > +{ + _phantom : core::marker::PhantomData< ( Context, Formed, End ) >, +} + +impl< Context, Formed, End > Default +for Struct1FormerDefinition< Context, Formed, End > +{ + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +#[ derive( Debug ) ] +pub struct Struct1FormerDefinitionTypes< Context = (), Formed = Struct1 > +{ + _phantom : core::marker::PhantomData< ( Context, Formed ) >, +} + +impl< Context, Formed > Default +for Struct1FormerDefinitionTypes< Context, Formed > +{ + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +impl< Context, Formed > former::FormerDefinitionTypes +for Struct1FormerDefinitionTypes< Context, Formed > +{ + type Storage = Struct1FormerStorage; + type Formed = Formed; + type Context = Context; +} + +impl< Context, Formed > former::FormerMutator +for Struct1FormerDefinitionTypes< Context, Formed > +{ +} + +impl< Context, Formed, End > former::FormerDefinition +for Struct1FormerDefinition< Context, Formed, End > +where + End : former::FormingEnd< Struct1FormerDefinitionTypes< Context, Formed > >, +{ + type Types = Struct1FormerDefinitionTypes< Context, Formed >; + type End = End; + type Storage = Struct1FormerStorage; + type Formed = Formed; + type Context = Context; +} + +// = storage // generated by former pub struct Struct1FormerStorage @@ -49,32 +112,24 @@ impl Default for Struct1FormerStorage } -// - -pub struct Struct1Former -< - __FormerContext = Struct1, - __FormerEnd = the_module::ReturnFormed, -> -where - __FormerEnd : the_module::FormingEnd< Struct1, __FormerContext >, +impl former::Storage +for Struct1FormerStorage { - storage : Struct1FormerStorage, - context : core::option::Option< __FormerContext >, - on_end : core::option::Option< __FormerEnd >, + type Preformed = Struct1; } -impl< __FormerContext, __FormerEnd > Struct1Former< __FormerContext, __FormerEnd > -where - __FormerEnd: the_module::FormingEnd, +impl former::StoragePreform +for Struct1FormerStorage { + // type Preformed = Struct1; - fn form( mut self ) -> Struct1 + // fn preform( mut self ) -> < Self as former::Storage >::Formed + fn preform( mut self ) -> Self::Preformed { - let int_1 = if self.storage.int_1.is_some() + let int_1 = if self.int_1.is_some() { - self.storage.int_1.take().unwrap() + self.int_1.take().unwrap() } else { @@ -82,9 +137,9 @@ where val }; - let string_1 = if self.storage.string_1.is_some() + let string_1 = if self.string_1.is_some() { - self.storage.string_1.take().unwrap() + self.string_1.take().unwrap() } else { @@ -92,24 +147,26 @@ where val }; - let int_optional_1 = if self.storage.int_optional_1.is_some() + let int_optional_1 = if self.int_optional_1.is_some() { - Some( self.storage.int_optional_1.take().unwrap() ) + Some( self.int_optional_1.take().unwrap() ) } else { None }; - let string_optional_1 = if self.storage.string_optional_1.is_some() + let string_optional_1 = if self.string_optional_1.is_some() { - Some( self.storage.string_optional_1.take().unwrap() ) + Some( self.string_optional_1.take().unwrap() ) } else { None }; + // Rust failt to use parameter here + // < < Self as former::Storage >::Definition::Types as former::FormerDefinitionTypes >::Formed Struct1 { int_1, @@ -120,52 +177,113 @@ where } +} + +// = former + +pub struct Struct1Former< Definition = Struct1FormerDefinition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, + // Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage >, +{ + storage : Definition::Storage, + context : core::option::Option< Definition::Context >, + on_end : core::option::Option< Definition::End >, +} + +impl< Definition > Struct1Former< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage >, +{ + #[ inline( always ) ] - pub fn perform(self) -> Struct1 + pub fn perform(self) -> < Definition::Types as former::FormerDefinitionTypes >::Formed { let result = self.form(); return result; } + // xxx : update description + #[ inline( always ) ] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin( None, None, on_end ) + } + #[ inline( always ) ] - pub fn new() -> Struct1Former + pub fn new_coercing< IntoEnd >( end : IntoEnd ) -> Self + where + IntoEnd : Into< Definition::End >, { - Struct1Former:: - < - Struct1, - the_module::ReturnFormed, - >::begin(None, the_module::ReturnFormed) + Self::begin_coercing + ( + None, + None, + end, + ) } #[ inline( always ) ] pub fn begin ( - context : core::option::Option< __FormerContext >, - on_end : __FormerEnd, + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : < Definition as former::FormerDefinition >::End, ) -> Self { + if storage.is_none() + { + storage = Some( core::default::Default::default() ); + } Self { - storage : core::default::Default::default(), - context : context, + storage : storage.unwrap(), + context, on_end : ::core::option::Option::Some( on_end ), } } #[ inline( always ) ] - pub fn end( mut self ) -> __FormerContext + pub fn begin_coercing< IntoEnd > + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : IntoEnd, + ) -> Self + where + IntoEnd : ::core::convert::Into< < Definition as former::FormerDefinition >::End > + { + if storage.is_none() + { + storage = Some( core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context, + on_end : ::core::option::Option::Some( ::core::convert::Into::into( on_end ) ), + } + } + + #[ inline( always ) ] + pub fn end( mut self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed { let on_end = self.on_end.take().unwrap(); let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) + former::FormingEnd::< Definition::Types >::call( &on_end, self.storage, context ) + } + + #[ inline( always ) ] + pub fn form( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + self.end() } pub fn int_1< Src >( mut self, src : Src ) -> Self where Src : core::convert::Into< i32 >, { debug_assert!( self.storage.int_1.is_none() ); - self.storage.int_1 = Some( src.into() ); + self.storage.int_1 = Some( ::core::convert::Into::into( src ) ); self } @@ -173,7 +291,7 @@ where where Src : core::convert::Into< String >, { debug_assert!( self.storage.string_1.is_none() ); - self.storage.string_1 = Some( src.into() ); + self.storage.string_1 = Some( ::core::convert::Into::into( src ) ); self } @@ -181,12 +299,24 @@ where where Src : core::convert::Into< String > { debug_assert!( self.storage.string_optional_1.is_none() ); - self.storage.string_optional_1 = Some( src.into() ); + self.storage.string_optional_1 = Some( ::core::convert::Into::into( src ) ); self } } +impl< Definition > Struct1Former< Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage, Formed = Struct1 >, + Definition::Storage : former::StoragePreform, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage, Formed = Struct1 >, +{ + pub fn preform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + former::StoragePreform::preform( self.storage ) + } +} + // -include!( "../only_test/primitives.rs" ); +include!( "./only_test/primitives.rs" ); diff --git a/module/core/former/tests/inc/former_tests/attribute_alias.rs b/module/core/former/tests/inc/former_tests/attribute_alias.rs index 1d5206bf94..a173d57182 100644 --- a/module/core/former/tests/inc/former_tests/attribute_alias.rs +++ b/module/core/former/tests/inc/former_tests/attribute_alias.rs @@ -10,18 +10,25 @@ tests_impls! fn test_alias() { #[ derive( Debug, PartialEq, the_module::Former ) ] + // #[ derive( Debug, PartialEq, the_module::Former ) ] #[ debug ] + // #[ derive( Debug, PartialEq ) ] pub struct AliasTestStruct { - #[ alias( first_field ) ] + #[ scalar( name = first_field ) ] string_field : String, - #[ alias( second_field ) ] + #[ scalar( name = second_field ) ] i32_field : i32, i8_field : i8, } + // == begin of generated + + // == end of generated + let test_struct = AliasTestStruct::former() .first_field( "first_field" ) - .i32_field( 2 ) + .second_field( 2 ) + // .i32_field( 2 ) .i8_field( 1 ) .form(); diff --git a/module/core/former/tests/inc/former_tests/attribute_default_conflict.rs b/module/core/former/tests/inc/former_tests/attribute_default_conflict.rs new file mode 100644 index 0000000000..6a930e1014 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/attribute_default_conflict.rs @@ -0,0 +1,32 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq, Default, the_module::Former ) ] +pub struct Struct1 +{ + #[ former( default = 31 ) ] + pub int_1 : i32, +} + +// + +tests_impls! +{ + fn test_complex() + { + let command = Struct1::former().form(); + + let expected = Struct1 + { + int_1 : 31, + }; + a_id!( command, expected ); + } +} + +// + +tests_index! +{ + test_complex, +} diff --git a/module/core/former/tests/inc/former_tests/attribute_default_container.rs b/module/core/former/tests/inc/former_tests/attribute_default_container.rs index fab0ba40cf..a1e1e07132 100644 --- a/module/core/former/tests/inc/former_tests/attribute_default_container.rs +++ b/module/core/former/tests/inc/former_tests/attribute_default_container.rs @@ -8,18 +8,18 @@ use std::collections::HashSet; pub struct Struct1 { - #[ default( vec![ 1, 2, 3 ] ) ] + #[ former( default = vec![ 1, 2, 3 ] ) ] vec_ints : Vec< i32 >, - #[ default( hmap!{ 1 => 11 } ) ] + #[ former( default = hmap!{ 1 => 11 } ) ] hashmap_ints : HashMap< i32, i32 >, - #[ default( hset!{ 11 } ) ] + #[ former( default = hset!{ 11 } ) ] hashset_ints : HashSet< i32 >, - #[ default( vec![ "abc".to_string(), "def".to_string() ] ) ] + #[ former( default = vec![ "abc".to_string(), "def".to_string() ] ) ] vec_strings : Vec< String >, - #[ default( hmap!{ "k1".to_string() => "v1".to_string() } ) ] + #[ former( default = hmap!{ "k1".to_string() => "v1".to_string() } ) ] hashmap_strings : HashMap< String, String >, - #[ default( hset!{ "k1".to_string() } ) ] + #[ former( default = hset!{ "k1".to_string() } ) ] hashset_strings : HashSet< String >, } diff --git a/module/core/former/tests/inc/former_tests/attribute_default_primitive.rs b/module/core/former/tests/inc/former_tests/attribute_default_primitive.rs index e81ac264bf..609915ad5a 100644 --- a/module/core/former/tests/inc/former_tests/attribute_default_primitive.rs +++ b/module/core/former/tests/inc/former_tests/attribute_default_primitive.rs @@ -1,43 +1,24 @@ #[ allow( unused_imports ) ] use super::*; -// #[ allow( unused_imports ) ] -// use test_tools::exposed::*; -// -// only_for_aggregating_module! -// { -// #[ allow( unused_imports ) ] -// use wtools::meta::*; -// #[ allow( unused_imports ) ] -// use wtools::the_module::Former; -// } -// -// only_for_terminal_module! -// { -// #[ allow( unused_imports ) ] -// use meta_tools::*; -// #[ allow( unused_imports ) ] -// use the_module::Former; -// } - use std::collections::HashMap; use std::collections::HashSet; #[ derive( Debug, PartialEq, the_module::Former ) ] pub struct Struct1 { - #[ default( 31 ) ] + #[ former( default = 31 ) ] pub int_1 : i32, - #[ default( "abc" ) ] + #[ former( default = "abc" ) ] string_1 : String, - #[ default( 31 ) ] + #[ former( default = 31 ) ] int_optional_1 : Option< i32 >, - #[ default( "abc" ) ] + #[ former( default = "abc" ) ] string_optional_1 : Option< String >, vec_1 : Vec< String >, - hashmap_strings_1 : HashMap< String, String >, - hashset_strings_1 : HashSet< String >, + hashmap_1 : HashMap< String, String >, + hashset_1 : HashSet< String >, } // @@ -55,8 +36,8 @@ tests_impls! int_optional_1 : Some( 31 ), string_optional_1 : Some( "abc".to_string() ), vec_1 : vec![], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{}, + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, }; a_id!( command, expected ); } diff --git a/module/core/former/tests/inc/former_tests/attribute_feature.rs b/module/core/former/tests/inc/former_tests/attribute_feature.rs new file mode 100644 index 0000000000..20dea37cf8 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/attribute_feature.rs @@ -0,0 +1,43 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq ) ] +pub struct BaseCase +{ + #[ cfg( feature = "enabled" ) ] + enabled : i32, + #[ cfg( feature = "disabled" ) ] + disabled : i32, +} + +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Foo +{ + #[ cfg( feature = "enabled" ) ] + #[ allow( dead_code ) ] + enabled : i32, + #[ cfg( feature = "disabled" ) ] + disabled : i32, +} + +// == begin of generated + +// == end of generated + +#[ test ] +fn basecase() +{ + let got = BaseCase { enabled : 13 }; + let exp = BaseCase { enabled : 13 }; + a_id!( got, exp ); +} + +#[ test ] +fn basic() +{ + let got = Foo::former().enabled( 13 ).form(); + let exp = Foo { enabled : 13 }; + a_id!( got, exp ); +} diff --git a/module/core/former/tests/inc/former_tests/attribute_perform.rs b/module/core/former/tests/inc/former_tests/attribute_perform.rs index b7e645bbfc..2eaaa75fa0 100644 --- a/module/core/former/tests/inc/former_tests/attribute_perform.rs +++ b/module/core/former/tests/inc/former_tests/attribute_perform.rs @@ -7,6 +7,8 @@ pub struct Struct0 pub int_1 : i32, } +// #[ derive( Debug, PartialEq ) ] +// #[ derive( Debug, PartialEq, the_module::Former ) ] #[ debug ] #[ derive( Debug, PartialEq, the_module::Former ) ] #[ perform( fn perform1< 'a >() -> Option< &'a str > ) ] pub struct Struct1 @@ -14,7 +16,9 @@ pub struct Struct1 pub int_1 : i32, } -// +// == begin of generated + +// == end of generated impl Struct1 { diff --git a/module/core/former/tests/inc/former_tests/attribute_setter.rs b/module/core/former/tests/inc/former_tests/attribute_setter.rs index 14e852373e..ee18f78657 100644 --- a/module/core/former/tests/inc/former_tests/attribute_setter.rs +++ b/module/core/former/tests/inc/former_tests/attribute_setter.rs @@ -5,13 +5,13 @@ use super::*; pub struct StructWithCustomSetters { ordinary : String, - #[ setter( false ) ] + #[ scalar( setter = false ) ] magic : String, } -impl< Context, End > StructWithCustomSettersFormer< Context, End > +impl< Definition > StructWithCustomSettersFormer< Definition > where - End: the_module::FormingEnd< StructWithCustomSetters, Context >, + Definition : former::FormerDefinition< Storage = StructWithCustomSettersFormerStorage >, { /// Custom alternative setter of ordinary field. diff --git a/module/core/former/tests/inc/former_tests/attribute_storage_with_end.rs b/module/core/former/tests/inc/former_tests/attribute_storage_with_end.rs new file mode 100644 index 0000000000..57d5f5f7da --- /dev/null +++ b/module/core/former/tests/inc/former_tests/attribute_storage_with_end.rs @@ -0,0 +1,96 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq, the_module::Former ) ] +#[ storage_fields( a : i32, b : Option< String > ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Struct1 +{ + c : String, +} + +pub struct Struct1CustomEnd +{ + _phantom : core::marker::PhantomData< ( (), ) >, +} + +// impl< Definition > Default for Struct1CustomEnd< Definition > +impl Default for Struct1CustomEnd +{ + + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + +} + +#[ automatically_derived ] +impl< Context, > former::FormingEnd +< + Struct1FormerDefinitionTypes< Context, Struct1 > +> +for Struct1CustomEnd +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : Struct1FormerStorage, + super_former : Option< Context >, + ) + -> Struct1 + { + let a = if let Some( a ) = storage.a + { + a + } + else + { + Default::default() + }; + let b = if let Some( b ) = storage.b + { + b + } + else + { + Default::default() + }; + Struct1 { c : format!( "{:?} - {}", a, b ) } + } +} + +// == begin of generated + +// == end of generated + +tests_impls! +{ + + fn test_complex() + { + // let got = Struct1::former().a( 13 ).b( "abc" ).c( "def" ).form(); + let end = Struct1CustomEnd::default(); + let got = Struct1Former + ::< Struct1FormerDefinition< (), Struct1, _ > > + ::new( end ) + .a( 13 ).b( "abc" ).c( "def" ).form(); + let exp = Struct1 + { + c : "13 - abc".to_string(), + }; + a_id!( got, exp ); + } + +} + +tests_index! +{ + test_complex, +} diff --git a/module/core/former/tests/inc/former_tests/attribute_storage_with_mutator.rs b/module/core/former/tests/inc/former_tests/attribute_storage_with_mutator.rs new file mode 100644 index 0000000000..57936294d3 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/attribute_storage_with_mutator.rs @@ -0,0 +1,51 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq, the_module::Former ) ] +#[ storage_fields( a : i32, b : Option< String > ) ] +#[ mutator( custom = true ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Struct1 +{ + c : String, +} + +// = former mutator + +impl< Context, Formed > former::FormerMutator +for Struct1FormerDefinitionTypes< Context, Formed > +{ + /// Mutates the context and storage of the entity just before the formation process completes. + #[ inline ] + fn form_mutation( storage : &mut Self::Storage, _context : &mut ::core::option::Option< Self::Context > ) + { + storage.a.get_or_insert_with( Default::default ); + storage.b.get_or_insert_with( Default::default ); + storage.c = Some( format!( "{:?} - {}", storage.a.unwrap(), storage.b.as_ref().unwrap() ) ); + } +} + +// == begin of generated + +// == end of generated + +tests_impls! +{ + + fn test_complex() + { + let got = Struct1::former().a( 13 ).b( "abc" ).c( "def" ).form(); + let exp = Struct1 + { + c : "13 - abc".to_string(), + }; + a_id!( got, exp ); + } + +} + +tests_index! +{ + test_complex, +} diff --git a/module/core/former/tests/inc/compiletime/former_bad_attr.rs b/module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.rs similarity index 100% rename from module/core/former/tests/inc/compiletime/former_bad_attr.rs rename to module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.rs diff --git a/module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.stderr b/module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.stderr new file mode 100644 index 0000000000..8162f72bf2 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/compiletime/field_attr_bad.stderr @@ -0,0 +1,11 @@ +error: Unknown field attribute #[defaultx(31)] + --> tests/inc/former_tests/compiletime/field_attr_bad.rs:6:3 + | +6 | #[ defaultx( 31 ) ] + | ^^^^^^^^^^^^^^^^^^^ + +error: cannot find attribute `defaultx` in this scope + --> tests/inc/former_tests/compiletime/field_attr_bad.rs:6:6 + | +6 | #[ defaultx( 31 ) ] + | ^^^^^^^^ diff --git a/module/core/former/tests/inc/compiletime/former_hashmap_without_parameter.rs b/module/core/former/tests/inc/former_tests/compiletime/hashmap_without_parameter.rs similarity index 100% rename from module/core/former/tests/inc/compiletime/former_hashmap_without_parameter.rs rename to module/core/former/tests/inc/former_tests/compiletime/hashmap_without_parameter.rs diff --git a/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.rs b/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.rs new file mode 100644 index 0000000000..a08670ab93 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.rs @@ -0,0 +1,11 @@ +use former::Former; + +#[ derive( Former ) ] +#[ defaultx ] +pub struct Struct1 +{ + int_1 : i32, +} + +fn main() +{} \ No newline at end of file diff --git a/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.stderr b/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.stderr new file mode 100644 index 0000000000..7425033f39 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/compiletime/struct_attr_bad.stderr @@ -0,0 +1,12 @@ +error: Known structure attirbutes are : `storage_fields`, `perform`, `debug`. + Unknown structure attribute : #[defaultx] + --> tests/inc/former_tests/compiletime/struct_attr_bad.rs:4:1 + | +4 | #[ defaultx ] + | ^^^^^^^^^^^^^ + +error: cannot find attribute `defaultx` in this scope + --> tests/inc/former_tests/compiletime/struct_attr_bad.rs:4:4 + | +4 | #[ defaultx ] + | ^^^^^^^^ diff --git a/module/core/former/tests/inc/compiletime/former_vector_without_parameter.rs b/module/core/former/tests/inc/former_tests/compiletime/vector_without_parameter.rs similarity index 100% rename from module/core/former/tests/inc/compiletime/former_vector_without_parameter.rs rename to module/core/former/tests/inc/former_tests/compiletime/vector_without_parameter.rs diff --git a/module/core/former/tests/inc/former_tests/container_former_common.rs b/module/core/former/tests/inc/former_tests/container_former_common.rs new file mode 100644 index 0000000000..3bc9c84dfc --- /dev/null +++ b/module/core/former/tests/inc/former_tests/container_former_common.rs @@ -0,0 +1,301 @@ +// #![ allow( dead_code ) ] + +use super::*; +#[ allow( unused_imports ) ] +use collection_tools::Vec; + +// + +#[ test ] +fn definitions() +{ + + pub fn f1< Definition >( _x : Definition ) + where + Definition : former::FormerDefinitionTypes, + { + } + + pub fn f2< Definition >( _x : Definition ) + where + Definition : former::FormerDefinition, + { + } + + pub fn f3< Definition, End >( _x : End ) + where + Definition : former::FormerDefinitionTypes, + End : former::FormingEnd< Definition >, + { + } + + f1( former::VectorDefinitionTypes::< String, (), Vec< String > >::default() ); + f2( former::VectorDefinition::< String, (), Vec< String >, the_module::NoEnd >::default() ); + f3::< former::VectorDefinitionTypes< String, (), Vec< String > >, the_module::ReturnStorage >( the_module::ReturnStorage ); + f3::< < former::VectorDefinition< String, (), Vec< String >, the_module::NoEnd > as the_module::FormerDefinition >::Types, the_module::ReturnStorage >( the_module::ReturnStorage ); + +} + +// + +#[ test ] +fn begin_and_custom_end() +{ + + // basic case + + fn return_13( _storage : Vec< String >, _context : Option< () > ) -> f32 + { + 13.1 + } + let got = the_module::VectorFormer::begin( None, None, return_13 ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13.1; + a_id!( got, exp ); + + let got = the_module::VectorFormer::new( return_13 ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13.1; + a_id!( got, exp ); + + // with a context + + fn context_plus_13( _storage : Vec< String >, context : Option< f32 > ) -> f32 + { + if let Some( context ) = context + { + 13.1 + context + } + else + { + 13.1 + } + } + let got = the_module::VectorFormer::begin( None, Some( 10.0 ), context_plus_13 ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 23.1; + a_id!( got, exp ); + + // + +} + +// + +#[ test ] +fn custom_definition() +{ + + struct Return13; + impl former::FormerDefinitionTypes for Return13 + { + type Storage = Vec< String >; + type Formed = i32; + type Context = (); + } + + impl former::FormerMutator + for Return13 + { + } + + impl former::FormerDefinition for Return13 + { + type Types = Return13; + type End = Return13; + type Storage = Vec< String >; + type Formed = i32; + type Context = (); + } + + // - + + impl former::FormingEnd< Return13 > + for Return13 + { + fn call + ( + &self, + _storage : < Return13 as former::FormerDefinitionTypes >::Storage, + _context : Option< < Return13 as former::FormerDefinitionTypes >::Context > + ) -> < Return13 as former::FormerDefinitionTypes >::Formed + { + 13 + } + } + + // + + let got = former::ContainerFormer::< String, Return13 >::begin( None, None, Return13 ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + let got = former::ContainerFormer::< String, Return13 >::new( Return13 ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + // + +} + +// + +#[ test ] +fn custom_definition_parametrized() +{ + + struct Return13< E >( ::core::marker::PhantomData< E > ); + + impl< E > Return13< E > + { + pub fn new() -> Self + { + Self ( ::core::marker::PhantomData ) + } + } + + impl< E > former::FormerDefinitionTypes for Return13< E > + { + type Storage = Vec< E >; + type Formed = i32; + type Context = (); + } + + impl< E > former::FormerMutator + for Return13< E > + { + } + + impl< E > former::FormerDefinition for Return13< E > + { + type Types = Return13< E >; + type End = Return13< E >; + type Storage = Vec< E >; + type Formed = i32; + type Context = (); + } + + // - + + impl< E > the_module::FormingEnd< Return13< E > > + for Return13< E > + { + fn call + ( + &self, + _storage : < Return13< E > as the_module::FormerDefinitionTypes >::Storage, + _context : Option< < Return13< E > as the_module::FormerDefinitionTypes >::Context > + ) -> < Return13< E > as the_module::FormerDefinitionTypes >::Formed + { + 13 + } + } + + // + + let got = the_module::ContainerFormer::< String, Return13< String > >::begin_coercing( None, None, Return13::new() ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + let got = the_module::ContainerFormer::< String, Return13< String > >::new_coercing( Return13::new() ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + // + + type MyContainer< E > = the_module::ContainerFormer::< E, Return13< E > >; + + let got = MyContainer::< String >::begin_coercing( None, None, Return13::new() ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + let got = MyContainer::< String >::new_coercing( Return13::new() ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + // + +} + +// + +#[ test ] +fn custom_definition_custom_end() +{ + + struct Return13; + impl former::FormerDefinitionTypes for Return13 + { + type Storage = Vec< String >; + type Formed = i32; + type Context = (); + } + impl former::FormerMutator + for Return13 + { + } + impl former::FormerDefinition for Return13 + { + type Types = Return13; + type End = former::FormingEndClosure< < Self as former::FormerDefinition >::Types >; + type Storage = Vec< String >; + type Formed = i32; + type Context = (); + } + + fn return_13( _storage : Vec< String >, _context : Option< () > ) -> i32 + { + 13 + } + + let end_wrapper : the_module::FormingEndClosure< Return13 > = the_module::FormingEndClosure::new( return_13 ); + let got = the_module::ContainerFormer::< String, Return13 >::new( end_wrapper ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + let got = the_module::ContainerFormer::< String, Return13 >::new( return_13.into() ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + let got = the_module::ContainerFormer::< String, Return13 >::new_coercing( return_13 ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = 13; + a_id!( got, exp ); + + // + +} + +// diff --git a/module/core/former/tests/inc/former_tests/container_former_hashmap.rs b/module/core/former/tests/inc/former_tests/container_former_hashmap.rs new file mode 100644 index 0000000000..1b34ebbb3b --- /dev/null +++ b/module/core/former/tests/inc/former_tests/container_former_hashmap.rs @@ -0,0 +1,140 @@ +#![ allow( dead_code ) ] + +#[ allow( unused_imports ) ] +use super::*; +#[ allow( unused_imports ) ] +use collection_tools::HashMap; + +// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] +#[ cfg( not( feature = "use_alloc" ) ) ] +#[ test ] +fn add() +{ + + // expliccit with ContainerFormer + + let got : HashMap< String, String > = the_module + ::ContainerFormer + ::< ( String, String ), former::HashMapDefinition< String, String, (), HashMap< String, String >, the_module::ReturnStorage > > + ::new( former::ReturnStorage ) + .add( ( "a".into(), "x".into() ) ) + .add( ( "b".into(), "y".into() ) ) + .form(); + let exp = hmap! + [ + "a".to_string() => "x".to_string(), + "b".to_string() => "y".to_string(), + ]; + a_id!( got, exp ); + + // expliccit with HashMapFormer + + let got : HashMap< String, String > = the_module::HashMapFormer::< String, String, (), HashMap< String, String >, the_module::ReturnStorage > + ::new( former::ReturnStorage ) + .add( ( "a".into(), "x".into() ) ) + .add( ( "b".into(), "y".into() ) ) + .form(); + let exp = hmap! + [ + "a".to_string() => "x".to_string(), + "b".to_string() => "y".to_string(), + ]; + a_id!( got, exp ); + + // compact with HashMapFormer + + let got : HashMap< String, String > = the_module::HashMapFormer::new( former::ReturnStorage ) + .add( ( "a".into(), "x".into() ) ) + .add( ( "b".into(), "y".into() ) ) + .form(); + let exp = hmap! + [ + "a".to_string() => "x".to_string(), + "b".to_string() => "y".to_string(), + ]; + a_id!( got, exp ); + + // with begin + + let got : HashMap< String, String > = the_module::HashMapFormer + ::begin( Some( hmap![ "a".to_string() => "x".to_string() ] ), Some( () ), former::ReturnStorage ) + .add( ( "b".into(), "y".into() ) ) + .form(); + let exp = hmap! + [ + "a".to_string() => "x".to_string(), + "b".to_string() => "y".to_string(), + ]; + a_id!( got, exp ); + + // with help of ext + + use the_module::HashMapExt; + let got : HashMap< String, String > = HashMap::former() + .add( ( "a".into(), "x".into() ) ) + .add( ( "b".into(), "y".into() ) ) + .form(); + let exp = hmap! + [ + "a".to_string() => "x".to_string(), + "b".to_string() => "y".to_string(), + ]; + a_id!( got, exp ); + + // + +} + +// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] +#[ cfg( not( feature = "use_alloc" ) ) ] +#[ test ] +fn replace() +{ + + let got : HashMap< String, String > = the_module::HashMapFormer::new( former::ReturnStorage ) + .add( ( "x".to_string(), "y".to_string() ) ) + .replace( hmap![ "a".to_string() => "x".to_string(), "b".to_string() => "y".to_string(), ] ) + .form(); + let exp = hmap! + [ + "a".to_string() => "x".to_string(), + "b".to_string() => "y".to_string(), + ]; + a_id!( got, exp ); + +} + +#[ test ] +fn entry_to_val() +{ + let got = former::EntryToVal::< HashMap< u32, i32 > >::entry_to_val( ( 1u32, 13i32 ) ); + let exp = 13i32; + a_id!( got, exp ) +} + +#[ test ] +fn val_to_entry() +{ + + #[ derive( Clone, Copy, Debug, PartialEq ) ] + struct Val + { + key : u32, + data : i32, + } + + impl former::ValToEntry< HashMap< u32, Val > > for Val + { + type Entry = ( u32, Val ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.key, self ) + } + } + + let got = former::ValToEntry::< HashMap< u32, Val > >::val_to_entry( Val { key : 1u32, data : 13i32 } ); + let exp = ( 1u32, Val { key : 1u32, data : 13i32 } ); + a_id!( got, exp ) + +} diff --git a/module/core/former/tests/inc/former_tests/container_former_hashset.rs b/module/core/former/tests/inc/former_tests/container_former_hashset.rs new file mode 100644 index 0000000000..83b8e7a994 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/container_former_hashset.rs @@ -0,0 +1,121 @@ +#![ allow( dead_code ) ] + +#[ allow( unused_imports ) ] +use super::*; +#[ allow( unused_imports ) ] +use collection_tools::HashSet; + +// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] +#[ cfg( not( feature = "use_alloc" ) ) ] +#[ test ] +fn add() +{ + + // expliccit with ContainerFormer + + let got : HashSet< String > = the_module + ::ContainerFormer + ::< String, former::HashSetDefinition< String, (), HashSet< String >, the_module::ReturnStorage > > + ::new( former::ReturnStorage ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = hset! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // expliccit with HashSetFormer + + let got : HashSet< String > = the_module::HashSetFormer::< String, (), HashSet< String >, the_module::ReturnStorage > + ::new( former::ReturnStorage ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = hset! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // compact with HashSetFormer + + let got : HashSet< String > = the_module::HashSetFormer::new( former::ReturnStorage ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = hset! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // with begin_coercing + + let got : HashSet< String > = the_module::HashSetFormer + ::begin( Some( hset![ "a".to_string() ] ), Some( () ), former::ReturnStorage ) + .add( "b" ) + .form(); + let exp = hset! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // with help of ext + + use the_module::HashSetExt; + let got : HashSet< String > = HashSet::former() + .add( "a" ) + .add( "b" ) + .form(); + let exp = hset! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // + +} + +// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] +#[ cfg( not( feature = "use_alloc" ) ) ] +#[ test ] +fn replace() +{ + + let got : HashSet< String > = the_module::HashSetFormer::new( former::ReturnStorage ) + .add( "x" ) + .replace( hset![ "a".to_string(), "b".to_string() ] ) + .form(); + let exp = hset! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + +} + +#[ test ] +fn entry_to_val() +{ + let got = former::EntryToVal::< HashSet< i32 > >::entry_to_val( 13i32 ); + let exp = 13i32; + a_id!( got, exp ) +} + +#[ test ] +fn val_to_entry() +{ + let got = former::ValToEntry::< HashSet< i32 > >::val_to_entry( 13i32 ); + let exp = 13i32; + a_id!( got, exp ) +} diff --git a/module/core/former/tests/inc/former_tests/container_former_vec.rs b/module/core/former/tests/inc/former_tests/container_former_vec.rs new file mode 100644 index 0000000000..ec76210448 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/container_former_vec.rs @@ -0,0 +1,158 @@ +// #![ allow( dead_code ) ] + +use super::*; +#[ allow( unused_imports ) ] +use collection_tools::Vec; + +// + +#[ test ] +fn add() +{ + + // expliccit with ContainerFormer + + let got : Vec< String > = the_module + ::ContainerFormer + ::< String, former::VectorDefinition< String, (), Vec< String >, the_module::ReturnStorage > > + ::new( former::ReturnStorage ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = vec! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // expliccit with VectorFormer + + let got : Vec< String > = the_module::VectorFormer::< String, (), Vec< String >, the_module::ReturnStorage > + ::new( former::ReturnStorage ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = vec! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // compact with VectorFormer + + let got : Vec< String > = the_module::VectorFormer::new( former::ReturnStorage ) + .add( "a" ) + .add( "b" ) + .form(); + let exp = vec! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // with begin_coercing + + let got : Vec< String > = the_module::VectorFormer + ::begin( Some( vec![ "a".to_string() ] ), Some( () ), former::ReturnStorage ) + .add( "b" ) + .form(); + let exp = vec! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // with help of ext + + use the_module::VecExt; + let got : Vec< String > = Vec::former() + .add( "a" ) + .add( "b" ) + .form(); + let exp = vec! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + + // + +} + +// + +#[ test ] +fn replace() +{ + + let got : Vec< String > = the_module::VectorFormer::new( former::ReturnStorage ) + .add( "x" ) + .replace( vec![ "a".to_string(), "b".to_string() ] ) + .form(); + let exp = vec! + [ + "a".to_string(), + "b".to_string(), + ]; + a_id!( got, exp ); + +} + +// + +// qqq : make similar test for all containers +#[ test ] +fn entity_to() +{ + + // qqq : uncomment and make it working + // let got = < Vec< i32 > as former::EntityToFormer< former::VectorDefinition< i32, (), Vec< i32 >, former::ReturnPreformed > > > + // ::Former::new( former::ReturnPreformed ) + // .add( 13 ) + // .form(); + // let exp = vec![ 13 ]; + // a_id!( got, exp ); + +// qqq : uncomment and make it working +// let got = < Vec< i32 > as former::EntityToStorage >::Storage::default(); +// let exp = +// < +// Vec< i32 > as former::EntityToFormer +// < +// Vec< i32 >FormerDefinition< (), Vec< i32 >, former::ReturnPreformed > +// > +// >::Former::new( former::ReturnPreformed ); +// a_id!( got.int_1, exp.storage.int_1 ); +// +// let got = < Vec< i32 > as former::EntityToStorage >::Storage::default(); +// let exp = +// < +// Vec< i32 > as former::EntityToFormer +// < +// < Vec< i32 > as former::EntityToDefinition< (), Vec< i32 >, former::ReturnPreformed > >::Definition +// > +// >::Former::new( former::ReturnPreformed ); +// a_id!( got.int_1, exp.storage.int_1 ); + +} + +#[ test ] +fn entry_to_val() +{ + let got = former::EntryToVal::< Vec< i32 > >::entry_to_val( 13i32 ); + let exp = 13i32; + a_id!( got, exp ) +} + +#[ test ] +fn val_to_entry() +{ + let got = former::ValToEntry::< Vec< i32 > >::val_to_entry( 13i32 ); + let exp = 13i32; + a_id!( got, exp ) +} diff --git a/module/core/former/tests/inc/former_tests/name_collision_context.rs b/module/core/former/tests/inc/former_tests/name_collision_context.rs index e9f7024853..8123626a1d 100644 --- a/module/core/former/tests/inc/former_tests/name_collision_context.rs +++ b/module/core/former/tests/inc/former_tests/name_collision_context.rs @@ -5,6 +5,7 @@ use super::*; pub trait CloneAny{} pub trait End{} +pub trait Formed{} pub trait OnEnd{} #[ derive( Clone, the_module::Former ) ] diff --git a/module/core/former/tests/inc/former_tests/name_collision_end.rs b/module/core/former/tests/inc/former_tests/name_collision_end.rs index f0200c1c34..99f736019d 100644 --- a/module/core/former/tests/inc/former_tests/name_collision_end.rs +++ b/module/core/former/tests/inc/former_tests/name_collision_end.rs @@ -5,10 +5,17 @@ use super::*; pub trait CloneAny{} pub trait Context{} +pub trait Formed{} pub trait OnEnd{} #[ derive( Clone, the_module::Former ) ] +// #[ derive( Clone, the_module::Former ) ] #[ debug ] +// #[ derive( Clone ) ] pub struct End { inner : std::sync::Arc< core::cell::RefCell< dyn CloneAny > > } + +// = begin_coercing of generated + +// == end of generated \ No newline at end of file diff --git a/module/core/former/tests/inc/former_tests/name_collisions.rs b/module/core/former/tests/inc/former_tests/name_collisions.rs index 5aef9ca9f1..843af61803 100644 --- a/module/core/former/tests/inc/former_tests/name_collisions.rs +++ b/module/core/former/tests/inc/former_tests/name_collisions.rs @@ -28,10 +28,10 @@ type HashMap = (); pub struct Struct1 { vec_1 : Vec< String >, - hashmap_strings_1 : std::collections::HashMap< String, String >, - hashset_strings_1 : std::collections::HashSet< String >, + hashmap_1 : std::collections::HashMap< String, String >, + hashset_1 : std::collections::HashSet< String >, } // -include!( "../only_test/containers_without_runtime.rs" ); +include!( "./only_test/containers_without_subformer.rs" ); diff --git a/module/core/former/tests/inc/former_tests/only_test/basic.rs b/module/core/former/tests/inc/former_tests/only_test/basic.rs new file mode 100644 index 0000000000..a4b4dbf907 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/basic.rs @@ -0,0 +1,562 @@ +#[ allow( unused_imports ) ] +use super::*; + +// + +tests_impls! +{ + + // + + fn internals() + { + + let former = Struct1::former(); + a_id!( former.storage.int_1, None ); + a_id!( former.context, None ); + a_id!( print!( "{:?}", former.on_end ), print!( "{:?}", Some( the_module::ReturnPreformed ) ) ); + let former2 = Struct1Former::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > >::new_coercing( former::ReturnPreformed ); + a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); + let former2 = Struct1Former::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > >::new( former::ReturnPreformed ); + a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); + + let command = Struct1::former().form(); + a_id!( command.int_1, 0 ); + + let command = Struct1::former().perform(); + a_id!( command.int_1, 0 ); + + let command = Struct1::former().end(); + a_id!( command.int_1, 0 ); + + } + + // + + fn entity_to() + { + + let got = < Struct1 as former::EntityToFormer< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > > >::Former::new( former::ReturnPreformed ) + .int_1( 13 ) + .form(); + let exp = Struct1 { int_1 : 13 }; + a_id!( got, exp ); + + let got = < Struct1 as former::EntityToStorage >::Storage::default(); + let exp = + < + Struct1 as former::EntityToFormer + < + Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > + > + >::Former::new( former::ReturnPreformed ); + a_id!( got.int_1, exp.storage.int_1 ); + + let got = < Struct1 as former::EntityToStorage >::Storage::default(); + let exp = + < + Struct1 as former::EntityToFormer + < + < Struct1 as former::EntityToDefinition< (), Struct1, former::ReturnPreformed > >::Definition + > + >::Former::new( former::ReturnPreformed ); + a_id!( got.int_1, exp.storage.int_1 ); + + } + + // + + fn former_begin() + { + + let former = < Struct1Former as former::FormerBegin< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > > > + ::former_begin( None, None, former::ReturnPreformed ); + let got = former + .int_1( 13 ) + .form(); + let exp = Struct1 { int_1 : 13 }; + a_id!( got, exp ); + + } + + // + + fn custom_definition_params() + { + + // custom params + let got = Struct1Former + :: + < + Struct1FormerDefinition< i32, i32, former::FormingEndClosure< Struct1FormerDefinitionTypes< i32, i32 > > > + > + ::begin_coercing + ( + None, + Some( 3 ), + | storage : Struct1FormerStorage, context : Option< i32 > | { 2 * ( storage.int_1.unwrap() + context.unwrap() ) }, + ) + .int_1( 13 ) + .form(); + a_id!( got, 32 ); + + // custom params with into + let got = Struct1Former + :: + < + Struct1FormerDefinition< i32, i32, former::FormingEndClosure< Struct1FormerDefinitionTypes< i32, i32 > > > + > + ::begin_coercing + ( + None, + Some( 3 ), + | storage : Struct1FormerStorage, context : Option< i32 > | { 2 * ( storage.int_1.unwrap() + context.unwrap() ) }, + ) + .int_1( 13 ) + .form(); + a_id!( got, 32 ); + + // custom params begin_coercing + let got = Struct1Former + :: + < + + Struct1FormerDefinition< i32, i32, former::FormingEndClosure< Struct1FormerDefinitionTypes< i32, i32 > > > + > + ::begin_coercing + ( + None, + Some( 3 ), + | storage : Struct1FormerStorage, context : Option< i32 > | { 2 * ( storage.int_1.unwrap() + context.unwrap() ) } + ) + .int_1( 13 ) + .form(); + a_id!( got, 32 ); + + // custom params begin_coercing with Struct1FormerWithClosure + let got = Struct1Former + :: + < + + Struct1FormerDefinition< i32, i32, former::FormingEndClosure< Struct1FormerDefinitionTypes< i32, i32 > > > + > + ::begin_coercing + ( + None, + Some( 3 ), + | storage : Struct1FormerStorage, context : Option< i32 > | { 2 * ( storage.int_1.unwrap() + context.unwrap() ) } + ) + .int_1( 13 ) + .form(); + a_id!( got, 32 ); + + // less explicit + let got = Struct1Former + :: + < + + Struct1FormerDefinition< i32, i32, former::FormingEndClosure< _ > > + > + ::begin_coercing + ( + None, + Some( 3 ), + | storage : Struct1FormerStorage, context : Option< i32 > | { 2 * ( storage.int_1.unwrap() + context.unwrap() ) } + ) + .int_1( 13 ) + .form(); + a_id!( got, 32 ); + + } + + // + + fn begin_coercing() + { + + // begin_coercing with none + let got = Struct1Former::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > >::begin_coercing( None, None, the_module::ReturnPreformed ).int_1( 13 ).form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // begin_coercing with storage + let mut storage = Struct1FormerStorage::default(); + storage.int_1 = Some( 13 ); + let exp = Struct1Former::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > >::begin_coercing( Some( storage ), None, the_module::ReturnPreformed ).form(); + a_id!( got, exp ); + + // begin_coercing with context + let mut storage = Struct1FormerStorage::default(); + storage.int_1 = Some( 13 ); + let exp = Struct1Former + :: + < + Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > + > + ::begin_coercing( Some( storage ), Some( () ), the_module::ReturnPreformed ) + .form(); + a_id!( got, exp ); + + } + + // + + fn begin() + { + + // custom params + let got = Struct1Former + // ::< Struct1FormerDefinition< i32, i32, _ > > + :: + < + + Struct1FormerDefinition< i32, i32, _ > + > + ::begin + ( + None, + Some( 3 ), + former::FormingEndClosure::new + ( + | storage : Struct1FormerStorage, context | { 2 * ( storage.int_1.unwrap() + context.unwrap() ) } + ), + ) + .int_1( 13 ) + .form(); + a_id!( got, 32 ); + + // custom params with into + let got = Struct1Former + // ::< Struct1FormerDefinition< i32, i32, former::FormingEndClosure< Struct1FormerDefinitionTypes< i32, i32 > > > > + :: + < + + Struct1FormerDefinition< i32, i32, former::FormingEndClosure< Struct1FormerDefinitionTypes< i32, i32 > > > + > + ::begin + ( + None, + Some( 3 ), + ( + | storage : Struct1FormerStorage, context : Option< i32 > | { 2 * ( storage.int_1.unwrap() + context.unwrap() ) } + ).into(), + ) + .int_1( 13 ) + .form(); + a_id!( got, 32 ); + + } + + // + + fn new_coercing() + { + + // basic case + let former = Struct1::former(); + let former2 = Struct1Former::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > >::new( former::ReturnPreformed ); + a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); + let exp = former.form(); + let got = former2.form(); + a_id!( got, exp ); + + // default explicit params + let got = Struct1Former + ::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > > + ::new_coercing( former::ReturnPreformed ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // default explicit params with wrapper + fn f1( storage : Struct1FormerStorage, _context : Option< () > ) -> Struct1 + { + former::StoragePreform::preform( storage ) + } + let end_wrapper : former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > = former::FormingEndClosure::new( f1 ); + let got = Struct1Former + // ::< Struct1FormerDefinition< (), Struct1, former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > > > + :: + < + Struct1FormerDefinition< (), Struct1, former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > > + > + ::new_coercing( end_wrapper ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // default explicit params with wrapper and closure + let got = Struct1Former + // ::< Struct1FormerDefinition< (), Struct1, former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > > > + :: + < + + Struct1FormerDefinition< (), Struct1, former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > > + > + ::new_coercing( former::FormingEndClosure::new( | storage, _context | { former::StoragePreform::preform( storage ) } ) ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // default explicit params with wrapper and closure, auto types + let got = Struct1Former + // ::< Struct1FormerDefinition< _, _, former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > > > + :: + < + Struct1FormerDefinition< (), Struct1, former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > > + > + ::new_coercing( former::FormingEndClosure::new( | storage, _context : Option< () > | { former::StoragePreform::preform( storage ) } ) ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + } + + // + + fn new() + { + + // basic case + let former = Struct1::former(); + let former2 = Struct1Former::< Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > >::new( former::ReturnPreformed ); + a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); + let exp = former.form(); + let got = former2.form(); + a_id!( got, exp ); + + // default explicit params + let got = Struct1Former + // ::< Struct1FormerDefinition< (), Struct1, _ > > + :: + < + + Struct1FormerDefinition< (), Struct1, _ >, + > + ::new( former::ReturnPreformed ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // default explicit params with wrapper + fn f1( storage : Struct1FormerStorage, _context : Option< () > ) -> Struct1 + { + former::StoragePreform::preform( storage ) + } + let end_wrapper : former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > = former::FormingEndClosure::new( f1 ); + let got = Struct1Former + // ::< Struct1FormerDefinition< (), Struct1, _ > > + :: + < + Struct1FormerDefinition< (), Struct1, _ >, + > + ::new( end_wrapper ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // + + // default explicit params with wrapper and closure + let got = Struct1Former + // ::< Struct1FormerWithClosure< (), Struct1 > > + :: + < + Struct1FormerDefinition< (), Struct1, _ > + > + ::new( | storage, _context | { former::StoragePreform::preform( storage ) } ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // + + // default explicit params with wrapper and closure + let got = Struct1Former + // ::< Struct1FormerDefinition< (), Struct1, _ > > + :: + < + Struct1FormerDefinition< (), Struct1, _ >, + > + ::new( former::FormingEndClosure::new( | storage, _context | { former::StoragePreform::preform( storage ) } ) ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + // default explicit params with wrapper and closure, auto types + let got = Struct1Former + // ::< Struct1FormerDefinition< _, _, _ > > + :: + < + Struct1FormerDefinition< _, _, _ >, + > + ::new( former::FormingEndClosure::new( | storage, _context : Option< () > | { former::StoragePreform::preform( storage ) } ) ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + + } + + // + + fn preform() + { + + // formation should have method preform + let got = Struct1::former().preform(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // storage should have method preform + let got = the_module::StoragePreform::preform( Struct1::former().storage ); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // storage should have method preform + use the_module::StoragePreform; + let got = Struct1::former().storage.preform(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + } + + // + + fn definition() + { + + // default is implemented for definition + let _default = Struct1FormerDefinition::< (), Struct1, former::ReturnPreformed >::default(); + // let _default = Struct1FormerDefinition::default(); // why does not work? + + // definition types exists and has Formed + let got = < Struct1FormerDefinitionTypes< (), Struct1 > as the_module::FormerDefinitionTypes >::Formed::former().form(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // definition types exists and has Formed + let got = < Struct1FormerDefinitionTypes< (), Struct1 > as the_module::FormerDefinitionTypes >::Formed::former().form(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // definition types exists and has Storage + use former::StoragePreform; + let got = < Struct1FormerDefinitionTypes< (), Struct1 > as the_module::FormerDefinitionTypes >::Storage + ::preform( Struct1::former().storage ); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // definition exists and has Storage + let got = < < Struct1FormerDefinition< (), Struct1, former::ReturnPreformed > as the_module::FormerDefinition >::Types as the_module::FormerDefinitionTypes >::Formed + ::former().form(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + } + + // + + fn storage() + { + + // definition exists and has Storage + let got = < Struct1FormerStorage as the_module::StoragePreform >::preform( Struct1::former().storage ); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // default is implemented for Storage + let got = Struct1FormerStorage::default().preform(); + let exp = Struct1::former().storage.preform(); + a_id!( got, exp ); + + // definition exists and has Storage + use former::StoragePreform; + let got = Struct1::former().storage.preform(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // storage exists + let got = < Struct1FormerStorage as the_module::Storage >::Preformed::former().form(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + } + + // + + fn test_int() + { + + // test.case( "basic" ); + + let command = Struct1::former() + .int_1( 13 ) + .form(); + // dbg!( &command ); + + let expected = Struct1 + { + int_1 : 13, + }; + a_id!( command, expected ); + + // test.case( "rewriting" ); + + // should_throw( || + // { + // let _command = Struct1::former() + // .int_1( 1 ) + // .int_1( 3 ) + // .form(); + // Ok( () ) + // })?; + } + + // + + fn test_underscored_form() + { + // test.case( "basic" ); + let command = Struct1::former() + .int_1( 13 ) + .form(); + + let expected = Struct1 + { + int_1 : 13, + }; + a_id!( command, expected ); + } + + // + + +} + +// + +tests_index! +{ + internals, + entity_to, + former_begin, + custom_definition_params, + begin_coercing, + begin, + new_coercing, + new, + preform, + definition, + storage, + test_int, + test_underscored_form, +} diff --git a/module/core/former/tests/inc/former_tests/only_test/containers_with_subformer.rs b/module/core/former/tests/inc/former_tests/only_test/containers_with_subformer.rs new file mode 100644 index 0000000000..042d36e538 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/containers_with_subformer.rs @@ -0,0 +1,365 @@ +#[ allow( unused_imports ) ] +use super::*; + +// + +tests_impls! +{ + + // + + fn internals() + { + + // test.case( "vector : construction" ); + + // fields + let former = Struct1::former(); + a_id!( former.storage.vec_1, None ); + a_id!( former.storage.hashmap_1, None ); + a_id!( former.storage.hashset_1, None ); + a_id!( former.context, None ); + + // form + let got = Struct1::former().form(); + let exp = Struct1::default(); + a_id!( got, exp ); + + // preform + let got = Struct1::former().preform(); + let exp = Struct1::default(); + a_id!( got, exp ); + + // perform + let got = Struct1::former().perform(); + let exp = Struct1::default(); + a_id!( got, exp ); + + // end + let got = Struct1::former().end(); + let exp = Struct1::default(); + a_id!( got, exp ); + + } + + // + + fn new() + { + + // former with explicit definition + let former = Struct1::former(); + a_id!( print!( "{:?}", former.on_end ), print!( "{:?}", Some( the_module::ReturnPreformed ) ) ); + let former2 = Struct1Former::< Struct1FormerDefinition >::new_coercing( former::ReturnPreformed ); + a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); + + // default parameters + let former = Struct1::former(); + let former2 : Struct1Former = Struct1Former::new_coercing( former::ReturnPreformed ); + a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); + + // closure without helper + let got : Struct1 = Struct1Former + ::< Struct1FormerDefinition< _, _, former::FormingEndClosure< Struct1FormerDefinitionTypes< (), Struct1 > > > > + ::new_coercing( | storage : Struct1FormerStorage, _context | { former::StoragePreform::preform( storage ) } ) + .vec_1().replace( vec![ "a".to_string(), "b".to_string() ] ).end() + .form(); + let exp : Struct1 = Struct1 + { + vec_1 : vec![ "a".to_string(), "b".to_string() ], + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, + }; + a_id!( got, exp ); + + // closure with helper + let got : Struct1 = Struct1Former + ::< Struct1FormerDefinition< (), Struct1, _ > > + ::new( | storage, _context | { former::StoragePreform::preform( storage ) } ) + .vec_1().replace( vec![ "a".to_string(), "b".to_string() ] ).end() + .form(); + let exp : Struct1 = Struct1 + { + vec_1 : vec![ "a".to_string(), "b".to_string() ], + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, + }; + a_id!( got, exp ); + + // // closure with helper + // let got : Struct1 = Struct1Former + // ::< Struct1FormerWithClosure< (), Struct1 > > + // ::new_coercing( | storage : Struct1FormerStorage, _context | { former::StoragePreform::preform( storage ) } ) + // .vec_1().replace( vec![ "a".to_string(), "b".to_string() ] ).end() + // .form(); + // let exp : Struct1 = Struct1 + // { + // vec_1 : vec![ "a".to_string(), "b".to_string() ], + // hashmap_1 : hmap!{}, + // hashset_1 : hset!{}, + // }; + // a_id!( got, exp ); + + // closure with helper + let got : Struct1 = Struct1Former + ::< Struct1FormerDefinition< (), Struct1, _ > > + ::begin( None, None, | storage, _context | { former::StoragePreform::preform( storage ) } ) + .vec_1().replace( vec![ "a".to_string(), "b".to_string() ] ).end() + .form(); + let exp : Struct1 = Struct1 + { + vec_1 : vec![ "a".to_string(), "b".to_string() ], + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, + }; + a_id!( got, exp ); + + } + + // + + fn field_forming_end() + { + + // Container subformers are defined + let _got = Struct1FormerAssignVec1End::< Struct1FormerDefinition >::default(); + let _got = Struct1FormerAssignHashmap1End::< Struct1FormerDefinition >::default(); + let _got = Struct1FormerAssignHashset1End::< Struct1FormerDefinition >::default(); + + // AsSubformerEnd is defined + fn _f1< End : Struct1AsSubformerEnd< Struct1Former > > + ( + _end : End, + _subformer : Struct1AsSubformer< Struct1Former, impl Struct1AsSubformerEnd< Struct1Former > > + ) + { + } + + } + + // + + fn test_vector() + { + + // test.case( "vector : implicit construction" ); + + let command = Struct1::former() + .vec_1().add( "ghi" ).add( "klm" ).end() + .form() + ; + // dbg!( &command ); + + let expected = Struct1 + { + vec_1 : vec![ "ghi".to_string(), "klm".to_string() ], + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + + // test.case( "vector : replace" ); + + let command = Struct1::former() + .vec_1().replace( vec![ "a".to_string(), "bc".to_string(), "def".to_string() ] ).end() + .form(); + let expected = Struct1 + { + vec_1 : vec![ "a".to_string(), "bc".to_string(), "def".to_string() ], + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + + let command = Struct1::former() + .vec_1().add( "x" ).replace( vec![ "a".to_string(), "bc".to_string(), "def".to_string() ] ).end() + .form(); + let expected = Struct1 + { + vec_1 : vec![ "a".to_string(), "bc".to_string(), "def".to_string() ], + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + + // test.case( "vector : replace and add" ); + + let command = Struct1::former() + .vec_1().replace( vec![ "a".to_string(), "bc".to_string(), "def".to_string() ] ).add( "gh" ).end() + .form(); + // dbg!( &command ); + + let expected = Struct1 + { + vec_1 : vec![ "a".to_string(), "bc".to_string(), "def".to_string(), "gh".to_string() ], + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + } + + // + + fn test_hashmap() + { + + // test.case( "implicit construction" ); + + let command = Struct1::former() + .hashmap_1().add( ( "k1".to_string(), "v1".to_string() ) ).add( ( "k2".to_string(), "v2".to_string() ) ).end() + .form() + ; + // dbg!( &command ); + + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + + // test.case( "replace" ); + + let command = Struct1::former() + .hashmap_1().replace( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ).end() + .form() + ; + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + + let command = Struct1::former() + .hashmap_1().add( ( "x".to_string(), "v1".to_string() ) ).replace( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ).end() + .form() + ; + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + + // test.case( "replace and add" ); + + let command = Struct1::former() + .hashmap_1().replace( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ) + .add( ( "k3".to_string(), "v3".to_string() ) ).end() + .form() + ; + // dbg!( &command ); + + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string(), "k3".to_string() => "v3".to_string() }, + hashset_1 : hset!{}, + }; + a_id!( command, expected ); + } + + // + + fn test_hashset() + { + + // test.case( "implicit construction" ); + + let command = Struct1::former() + .hashset_1().add( "v1" ).add( "v2" ).end() + .form() + ; + // dbg!( &command ); + + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{}, + hashset_1 : hset!{ "v1".to_string(), "v2".to_string() }, + }; + a_id!( command, expected ); + + // test.case( "replace" ); + + let command = Struct1::former() + .hashset_1().replace( hset!{ "v1".to_string(), "v2".to_string() } ).end() + .form() + ; + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{}, + hashset_1 : hset!{ "v1".to_string(), "v2".to_string() }, + }; + a_id!( command, expected ); + + let command = Struct1::former() + .hashset_1().add( "x" ).replace( hset!{ "v1".to_string(), "v2".to_string() } ).end() + .form() + ; + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{}, + hashset_1 : hset!{ "v1".to_string(), "v2".to_string() }, + }; + a_id!( command, expected ); + + // test.case( "replace and add" ); + + let command = Struct1::former() + .hashset_1().replace( hset!{ "v1".to_string(), "v2".to_string() } ).add( "v3" ).end() + .form() + ; + // dbg!( &command ); + + let expected = Struct1 + { + vec_1 : vec![], + hashmap_1 : hmap!{}, + hashset_1 : hset!{ "v1".to_string(), "v2".to_string(), "v3".to_string() }, + }; + a_id!( command, expected ); + } + + // + + fn test_complex() + { + + let command = Struct1::former() + .vec_1().add( "ghi" ).add( "klm" ).end() + .hashmap_1().add( ( "k1".to_string(), "v1".to_string() ) ).add( ( "k2".to_string(), "v2".to_string() ) ).end() + .hashset_1().add( "k1" ).end() + .form(); + // dbg!( &command ); + + let expected = Struct1 + { + vec_1 : vec![ "ghi".to_string(), "klm".to_string() ], + hashmap_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, + hashset_1 : hset!{ "k1".to_string() }, + }; + a_id!( command, expected ); + + } + +} + +// + +tests_index! +{ + internals, + new, + field_forming_end, + test_vector, + test_hashmap, + test_hashset, + test_complex, +} diff --git a/module/core/former/tests/inc/only_test/containers_without_runtime.rs b/module/core/former/tests/inc/former_tests/only_test/containers_without_subformer.rs similarity index 63% rename from module/core/former/tests/inc/only_test/containers_without_runtime.rs rename to module/core/former/tests/inc/former_tests/only_test/containers_without_subformer.rs index 8141ddc9fd..c19e92eead 100644 --- a/module/core/former/tests/inc/only_test/containers_without_runtime.rs +++ b/module/core/former/tests/inc/former_tests/only_test/containers_without_subformer.rs @@ -15,27 +15,27 @@ tests_impls! let former = Struct1::former(); a_id!( former.storage.vec_1, None ); - a_id!( former.storage.hashmap_strings_1, None ); - a_id!( former.storage.hashset_strings_1, None ); + a_id!( former.storage.hashmap_1, None ); + a_id!( former.storage.hashset_1, None ); a_id!( former.context, None ); - a_id!( print!( "{:?}", former.on_end ), print!( "{:?}", Some( the_module::ReturnFormed ) ) ); - let former2 = Struct1Former::< Struct1, the_module::ReturnFormed >::new(); + a_id!( print!( "{:?}", former.on_end ), print!( "{:?}", Some( the_module::ReturnPreformed ) ) ); + let former2 = Struct1Former::< Struct1FormerDefinition >::new_coercing( the_module::ReturnPreformed ); a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); let command = Struct1::former().form(); a_id!( command.vec_1, Vec::< String >::new() ); - a_id!( command.hashmap_strings_1, hmap!{} ); - a_id!( command.hashset_strings_1, hset![] ); + a_id!( command.hashmap_1, hmap!{} ); + a_id!( command.hashset_1, hset![] ); let command = Struct1::former().perform(); a_id!( command.vec_1, Vec::< String >::new() ); - a_id!( command.hashmap_strings_1, hmap!{} ); - a_id!( command.hashset_strings_1, hset![] ); + a_id!( command.hashmap_1, hmap!{} ); + a_id!( command.hashset_1, hset![] ); let command = Struct1::former().end(); a_id!( command.vec_1, Vec::< String >::new() ); - a_id!( command.hashmap_strings_1, hmap!{} ); - a_id!( command.hashset_strings_1, hset![] ); + a_id!( command.hashmap_1, hmap!{} ); + a_id!( command.hashset_1, hset![] ); } @@ -55,8 +55,8 @@ tests_impls! let expected = Struct1 { vec_1 : vec![ "ghi".to_string(), "klm".to_string() ], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{}, + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, }; a_id!( command, expected ); } @@ -69,7 +69,7 @@ tests_impls! // test.case( "construction" ); let command = Struct1::former() - .hashmap_strings_1( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ) + .hashmap_1( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ) .form() ; // dbg!( &command ); @@ -77,8 +77,8 @@ tests_impls! let expected = Struct1 { vec_1 : vec![], - hashmap_strings_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, - hashset_strings_1 : hset!{}, + hashmap_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, + hashset_1 : hset!{}, }; a_id!( command, expected ); } @@ -90,7 +90,7 @@ tests_impls! // test.case( "construction" ); let command = Struct1::former() - .hashset_strings_1( hset!{ "v1".to_string(), "v2".to_string() } ) + .hashset_1( hset!{ "v1".to_string(), "v2".to_string() } ) .form() ; // dbg!( &command ); @@ -98,8 +98,8 @@ tests_impls! let expected = Struct1 { vec_1 : vec![], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{ "v1".to_string(), "v2".to_string() }, + hashmap_1 : hmap!{}, + hashset_1 : hset!{ "v1".to_string(), "v2".to_string() }, }; a_id!( command, expected ); } @@ -115,8 +115,8 @@ tests_impls! let expected = Struct1 { vec_1 : vec![], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{}, + hashmap_1 : hmap!{}, + hashset_1 : hset!{}, }; a_id!( command, expected ); } @@ -127,15 +127,15 @@ tests_impls! { let command = Struct1::former() .vec_1( vec![ "ghi".to_string(), "klm".to_string() ] ) - .hashmap_strings_1( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ) + .hashmap_1( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ) .form(); // dbg!( &command ); let expected = Struct1 { vec_1 : vec![ "ghi".to_string(), "klm".to_string() ], - hashmap_strings_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, - hashset_strings_1 : hset!{}, + hashmap_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, + hashset_1 : hset!{}, }; a_id!( command, expected ); @@ -150,10 +150,12 @@ tests_impls! tests_index! { + internals, test_vector, test_hashmap, test_hashset, test_underscored_form, test_complex, + } diff --git a/module/core/former/tests/inc/former_tests/only_test/parametrized_field.rs b/module/core/former/tests/inc/former_tests/only_test/parametrized_field.rs new file mode 100644 index 0000000000..7449ec7129 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/parametrized_field.rs @@ -0,0 +1,8 @@ + +#[ test ] +fn basic() +{ + let got = Child::< 'static, str >::former().name( "abc" ).arg( "arg1" ).end(); + let exp = Child::< 'static, str >{ name : "abc".into(), arg : "arg1" }; + a_id!( got, exp ); +} diff --git a/module/core/former/tests/inc/only_test/parametrized_struct.rs b/module/core/former/tests/inc/former_tests/only_test/parametrized_struct.rs similarity index 67% rename from module/core/former/tests/inc/only_test/parametrized_struct.rs rename to module/core/former/tests/inc/former_tests/only_test/parametrized_struct.rs index 01c03c9800..620e42198b 100644 --- a/module/core/former/tests/inc/only_test/parametrized_struct.rs +++ b/module/core/former/tests/inc/former_tests/only_test/parametrized_struct.rs @@ -3,10 +3,10 @@ fn command_form() { // form - let got = Command::< &str >::former() + let got = Child::< &str >::former() .name( "a" ) .form(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), properties : collection_tools::HashMap::< &str, Property< &str > >::new(), @@ -14,10 +14,10 @@ fn command_form() a_id!( got, exp ); // perform - let got = Command::< &str >::former() + let got = Child::< &str >::former() .name( "a" ) .perform(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), properties : collection_tools::HashMap::< &str, Property< &str > >::new(), @@ -25,10 +25,10 @@ fn command_form() a_id!( got, exp ); // end - let got = Command::< &str >::former() + let got = Child::< &str >::former() .name( "a" ) .end(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), properties : collection_tools::HashMap::< &str, Property< &str > >::new(), @@ -45,16 +45,16 @@ fn command_form() fn command_properties() { - // with HashMapSubformer - let got = Command::< &str >::former() + // with HashMapFormer + let got = Child::< &str >::former() .name( "a" ) .properties() - .insert( "property1", Property::< &str >::new( "property1", 13isize ) ) - .insert( "property2", Property::new( "property2", 13isize ) ) - .insert( "property2", Property::new( "property2", 113isize ) ) + .add( ( "property1", Property::< &str >::new( "property1", 13isize ) ) ) + .add( ( "property2", Property::new( "property2", 13isize ) ) ) + .add( ( "property2", Property::new( "property2", 113isize ) ) ) .end() .form(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), properties : hmap! diff --git a/module/core/former/tests/inc/only_test/primitives.rs b/module/core/former/tests/inc/former_tests/only_test/primitives.rs similarity index 84% rename from module/core/former/tests/inc/only_test/primitives.rs rename to module/core/former/tests/inc/former_tests/only_test/primitives.rs index 728105b6ad..c38fea9bf8 100644 --- a/module/core/former/tests/inc/only_test/primitives.rs +++ b/module/core/former/tests/inc/former_tests/only_test/primitives.rs @@ -8,45 +8,48 @@ tests_impls! // - fn internals() + fn api() { - // // test.case( "vector : construction" ); - // int_1, - // string_1, - // int_optional_1, - // string_optional_1, - - let former = Struct1::former(); - a_id!( former.storage.int_1, None ); - a_id!( former.storage.string_1, None ); - a_id!( former.storage.int_optional_1, None ); - a_id!( former.storage.string_optional_1, None ); - a_id!( former.context, None ); - a_id!( print!( "{:?}", former.on_end ), print!( "{:?}", Some( the_module::ReturnFormed ) ) ); - let former2 = Struct1Former::< Struct1, the_module::ReturnFormed >::new(); - a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); - + // form let command = Struct1::former().form(); a_id!( command.int_1, 0 ); a_id!( command.string_1, "".to_string() ); a_id!( command.int_optional_1, None ); a_id!( command.string_optional_1, None ); - let command = Struct1::former().perform(); + // end + let command = Struct1::former().end(); a_id!( command.int_1, 0 ); a_id!( command.string_1, "".to_string() ); a_id!( command.int_optional_1, None ); a_id!( command.string_optional_1, None ); - let command = Struct1::former().end(); + // perform + let command = Struct1::former().perform(); a_id!( command.int_1, 0 ); a_id!( command.string_1, "".to_string() ); a_id!( command.int_optional_1, None ); a_id!( command.string_optional_1, None ); + // formation should have method preform + let got = Struct1::former().preform(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // default explicit params with wrapper and closure + let got = Struct1Former + ::< Struct1FormerDefinition< (), Struct1, _ > > + ::new( | storage, _context | { former::StoragePreform::preform( storage ) } ) + .int_1( 13 ) + .form(); + let exp = Struct1::former().int_1( 13 ).form(); + a_id!( got, exp ); + } + // + fn test_int() { @@ -202,7 +205,7 @@ tests_impls! .int_1( 13 ) .string_1( "Abcd".to_string() ) // .vec_1().push( "ghi" ).push( "klm" ).end() - // .hashmap_strings_1().insert( "k1", "v1" ).insert( "k2", "v2" ).end() + // .hashmap_1().insert( "k1", "v1" ).insert( "k2", "v2" ).end() .string_optional_1( "dir1" ) .form(); // dbg!( &command ); @@ -228,7 +231,8 @@ tests_impls! tests_index! { - internals, + api, + test_int, test_string, test_optional_string, diff --git a/module/core/former/tests/inc/former_tests/only_test/string_slice.rs b/module/core/former/tests/inc/former_tests/only_test/string_slice.rs new file mode 100644 index 0000000000..2ed7eb90c5 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/string_slice.rs @@ -0,0 +1,101 @@ +#[ allow( unused_imports ) ] +use super::*; +#[ allow( unused_imports ) ] +use test_tools::exposed::*; + +// + +tests_impls! +{ + + + // + + fn api() + { + + // form + let command = Struct1::former().form(); + a_id!( command.string_slice_1, "" ); + + // end + let command = Struct1::former().end(); + a_id!( command.string_slice_1, "" ); + + // perform + let command = Struct1::former().perform(); + a_id!( command.string_slice_1, "" ); + + // formation should have method preform + let got = Struct1::former().preform(); + let exp = Struct1::former().form(); + a_id!( got, exp ); + + // default explicit params with wrapper and closure + let got = Struct1Former + ::< Struct1FormerDefinition< (), Struct1, _ > > + ::new( | storage, _context | { former::StoragePreform::preform( storage ) } ) + .string_slice_1( "abc" ) + .form(); + let exp = Struct1::former().string_slice_1( "abc" ).form(); + a_id!( got, exp ); + + // closure with helper + let got : Struct1 = Struct1Former + ::< Struct1FormerDefinition< (), Struct1, _ > > + ::begin( None, None, | storage, _context | { former::StoragePreform::preform( storage ) } ) + .string_slice_1( "abc" ) + .form(); + let exp = Struct1::former().string_slice_1( "abc" ).form(); + a_id!( got, exp ); + + } + + // + + fn test_complex() + { + // test.case( "default" ); + + let command = Struct1::former().form(); + let expected = Struct1 + { + string_slice_1 : "", + }; + a_id!( command, expected ); + + // test.case( "from slice" ); + + let command = Struct1::former() + .string_slice_1( "abc" ) + .form(); + let expected = Struct1 + { + string_slice_1 : "abc", + }; + a_id!( command, expected ); + +// // test.case( "from string" ); +// +// let command = Struct1::former() +// .string_slice_1( "abc".to_string() ) +// .form(); +// let expected = Struct1 +// { +// string_slice_1 : "abc", +// }; +// a_id!( command, expected ); + + } + + // + +} + +// + +tests_index! +{ + api, + test_complex, +} diff --git a/module/core/former/tests/inc/only_test/subformer_basic.rs b/module/core/former/tests/inc/former_tests/only_test/subformer_basic.rs similarity index 53% rename from module/core/former/tests/inc/only_test/subformer_basic.rs rename to module/core/former/tests/inc/former_tests/only_test/subformer_basic.rs index 6d8c236e49..ea039d9835 100644 --- a/module/core/former/tests/inc/only_test/subformer_basic.rs +++ b/module/core/former/tests/inc/former_tests/only_test/subformer_basic.rs @@ -1,11 +1,11 @@ -// let ca = wca::CommandsAggregator::former() -// .command( "echo" ) +// let ca = wca::ChildsParent::former() +// .command_with_closure( "echo" ) // .name( "prints all subjects and properties" ) // .subject( "Subject", wca::Type::String, true ) // .property( "property", "simple property", wca::Type::String, true ) // .routine( f1 ) // .perform() -// .command( "exit" ) +// .command_with_closure( "exit" ) // .name( "just exit" ) // .routine( || exit() ) // .perform() @@ -13,17 +13,17 @@ // ; // ca.execute( input ).unwrap(); -// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] +// qqq : for Antont : zzz : here and in all similar tests remove `#[ cfg( not( feature = "use_alloc" ) ) ]` #[ cfg( not( feature = "use_alloc" ) ) ] #[ test ] -fn command() +fn command_with_closure() { - let got = Command::< &str >::former() + let got = Child::< &str >::former() .name( "a" ) .subject( "b" ) .form(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), subject : "b".to_string(), @@ -31,11 +31,11 @@ fn command() }; a_id!( got, exp ); - let got = Command::< &str >::former() + let got = Child::< &str >::former() .name( "a" ) .subject( "b" ) .perform(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), subject : "b".to_string(), @@ -43,11 +43,11 @@ fn command() }; a_id!( got, exp ); - let got = Command::< &str >::former() + let got = Child::< &str >::former() .name( "a" ) .subject( "b" ) .end(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), subject : "b".to_string(), @@ -66,14 +66,14 @@ fn command_properties() { // with helper - let got = Command::< &str >::former() + let got = Child::< &str >::former() .name( "a" ) .subject( "b" ) .property( "property1", "simple property", 13isize ) .property( "property2", "simple property 2", 13isize ) .property( "property2", "simple property 3", 113isize ) .form(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), subject : "b".to_string(), @@ -86,17 +86,17 @@ fn command_properties() }; a_id!( got, exp ); - // with HashMapSubformer - let got = Command::< &str >::former() + // with HashMapFormer + let got = Child::< &str >::former() .name( "a" ) .subject( "b" ) .properties() - .insert( "property1", Property::new( "property1", "simple property", 13isize ) ) - .insert( "property2", Property::new( "property2", "simple property 2", 13isize ) ) - .insert( "property2", Property::new( "property2", "simple property 3", 113isize ) ) + .add( ( "property1", Property::new( "property1", "simple property", 13isize ) ) ) + .add( ( "property2", Property::new( "property2", "simple property 2", 13isize ) ) ) + .add( ( "property2", Property::new( "property2", "simple property 3", 113isize ) ) ) .end() .form(); - let exp = Command::< &str > + let exp = Child::< &str > { name : "a".to_string(), subject : "b".to_string(), @@ -120,50 +120,22 @@ fn aggregator() { // with helper - let got = Aggregator::< &str >::former() + let got = Parent::< &str >::former() .parameter1( "p1" ) - .commands().insert( "name1", CommandFormer::< &str >::new().name( "name1" ).subject( "s" ).end() ).end() - .command( "command1".to_string() ) - .subject( "b" ) - .property( "property1", "simple property", 13isize ) - .property( "property2", "simple property 3", 113isize ) - .end() - .command( "command2".to_string() ) - .subject( "c" ) - .property( "property3", "x", 113isize ) - .end() + .commands().add( ( "name1".to_string(), ChildFormer::< &str >::new_coercing( former::ReturnPreformed ).name( "name1" ).subject( "s" ).end() ) ).end() .form() ; - let name1 = Command::< &str > + let name1 = Child::< &str > { name : "name1".to_string(), subject : "s".to_string(), properties : hmap!{}, }; - let command1 = Command::< &str > - { - name : "command1".to_string(), - subject : "b".to_string(), - properties : hmap! - { - "property1" => Property::new( "property1", "simple property", 13isize ), - "property2" => Property::new( "property2", "simple property 3", 113isize ), - }, - }; - let command2 = Command::< &str > - { - name : "command2".to_string(), - subject : "c".to_string(), - properties : hmap! - { - "property3" => Property::new( "property3", "x", 113isize ), - }, - }; - let exp = Aggregator + let exp = Parent { parameter1 : "p1".to_string(), - commands : hmap!{ "name1" => name1, "command1" => command1, "command2" => command2 }, + commands : hmap!{ "name1" => name1 }, }; dbg!( &got ); dbg!( &exp ); @@ -177,31 +149,19 @@ fn aggregator() fn aggregator_alternative_form() { - let exp = Aggregator::< &str >::former() + let exp = Parent::< &str >::former() .parameter1( "p1" ) - .command( "command1".to_string() ) - .subject( "b" ) - .property( "property2", "simple property 3", 113isize ) - .end() .form() ; - let got = Aggregator::< &str >::former() + let got = Parent::< &str >::former() .parameter1( "p1" ) - .command( "command1".to_string() ) - .subject( "b" ) - .property( "property2", "simple property 3", 113isize ) - .end() .perform() ; a_id!( got, exp ); - let got = Aggregator::< &str >::former() + let got = Parent::< &str >::former() .parameter1( "p1" ) - .command( "command1".to_string() ) - .subject( "b" ) - .property( "property2", "simple property 3", 113isize ) - .end() .end() ; a_id!( got, exp ); diff --git a/module/core/former/tests/inc/former_tests/only_test/subformer_container.rs b/module/core/former/tests/inc/former_tests/only_test/subformer_container.rs new file mode 100644 index 0000000000..ce7f0f08dd --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/subformer_container.rs @@ -0,0 +1,21 @@ + +#[ test ] +fn basic() +{ + + let got = Parent::former() + .children() + .add( Child::former().name( "a" ).form() ) + .add( Child::former().name( "b" ).form() ) + .end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} diff --git a/module/core/former/tests/inc/former_tests/only_test/subformer_container_children2.rs b/module/core/former/tests/inc/former_tests/only_test/subformer_container_children2.rs new file mode 100644 index 0000000000..46fae25331 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/subformer_container_children2.rs @@ -0,0 +1,21 @@ + +#[ test ] +fn container() +{ + + let got = Parent::former() + .children2() + .add( Child::former().name( "a" ).form() ) + .add( Child::former().name( "b" ).form() ) + .end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} diff --git a/module/core/former/tests/inc/former_tests/only_test/subformer_scalar_children.rs b/module/core/former/tests/inc/former_tests/only_test/subformer_scalar_children.rs new file mode 100644 index 0000000000..76fe8fa988 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/subformer_scalar_children.rs @@ -0,0 +1,23 @@ + +#[ test ] +fn scalar() +{ + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let got = Parent::former() + .children( children ) + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} diff --git a/module/core/former/tests/inc/former_tests/only_test/subformer_scalar_children3.rs b/module/core/former/tests/inc/former_tests/only_test/subformer_scalar_children3.rs new file mode 100644 index 0000000000..ba2c97b459 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/subformer_scalar_children3.rs @@ -0,0 +1,23 @@ + +#[ test ] +fn scalar() +{ + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let got = Parent::former() + .children3( children ) + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} diff --git a/module/core/former/tests/inc/former_tests/only_test/subformer_subform_child.rs b/module/core/former/tests/inc/former_tests/only_test/subformer_subform_child.rs new file mode 100644 index 0000000000..f4167ee982 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/subformer_subform_child.rs @@ -0,0 +1,38 @@ + +#[ test ] +fn child() +{ + + let got = Parent::former() + .child( "a" ).end() + .child( "b" ).end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} + +#[ test ] +fn _child() +{ + + let got = Parent::former() + ._child().name( "a" ).end() + ._child().name( "b" ).end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} diff --git a/module/core/former/tests/inc/former_tests/only_test/subformer_subform_children2.rs b/module/core/former/tests/inc/former_tests/only_test/subformer_subform_children2.rs new file mode 100644 index 0000000000..c869113680 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/only_test/subformer_subform_children2.rs @@ -0,0 +1,19 @@ + +#[ test ] +fn subform() +{ + + let got = Parent::former() + .children2( "a" ).end() + .children2( "b" ).end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : false }, + Child { name : "b".to_string(), data : false }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} diff --git a/module/core/former/tests/inc/former_tests/parametrized_field.rs b/module/core/former/tests/inc/former_tests/parametrized_field.rs new file mode 100644 index 0000000000..fce1a22818 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/parametrized_field.rs @@ -0,0 +1,20 @@ +#![ allow( dead_code ) ] +#[ allow( unused_imports ) ] +use super::*; + +/// Parameter description. +#[ allow( explicit_outlives_requirements ) ] +#[ derive( Debug, PartialEq, the_module::Former ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Child< 'child, T : ?Sized + 'child > +{ + name : String, + arg : &'child T, +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/parametrized_field.rs" ); diff --git a/module/core/former/tests/inc/former_tests/parametrized_field_where.rs b/module/core/former/tests/inc/former_tests/parametrized_field_where.rs new file mode 100644 index 0000000000..baaaed538f --- /dev/null +++ b/module/core/former/tests/inc/former_tests/parametrized_field_where.rs @@ -0,0 +1,22 @@ +#![ allow( dead_code ) ] +#[ allow( unused_imports ) ] +use super::*; + +/// Parameter description. +#[ allow( explicit_outlives_requirements ) ] +#[ derive( Debug, PartialEq, the_module::Former ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Child< 'child, T > +where + T : ?Sized + 'child, +{ + name : String, + arg : &'child T, +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/parametrized_field.rs" ); diff --git a/module/core/former/tests/inc/former_tests/parametrized_struct_imm.rs b/module/core/former/tests/inc/former_tests/parametrized_struct_imm.rs index 2212f4c08b..d216575504 100644 --- a/module/core/former/tests/inc/former_tests/parametrized_struct_imm.rs +++ b/module/core/former/tests/inc/former_tests/parametrized_struct_imm.rs @@ -23,13 +23,17 @@ impl< Name > Property< Name > } #[ derive( Debug, PartialEq, the_module::Former ) ] -pub struct Command< K : core::hash::Hash + std::cmp::Eq > +// #[ derive( Debug, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Child< K : core::hash::Hash + std::cmp::Eq > { pub name : String, - #[ subformer( the_module::HashMapSubformer ) ] + #[ container( definition = former::HashMapDefinition ) ] pub properties : collection_tools::HashMap< K, Property< K > >, } -// == +// == begin_coercing of generated -include!( "../only_test/parametrized_struct.rs" ); +// == end of generated + +include!( "./only_test/parametrized_struct.rs" ); diff --git a/module/core/former/tests/inc/former_tests/parametrized_struct_manual.rs b/module/core/former/tests/inc/former_tests/parametrized_struct_manual.rs index ae57ca7e08..95e081b39e 100644 --- a/module/core/former/tests/inc/former_tests/parametrized_struct_manual.rs +++ b/module/core/former/tests/inc/former_tests/parametrized_struct_manual.rs @@ -22,182 +22,350 @@ impl< Name > Property< Name > } } +// #[ derive( Debug, PartialEq, the_module::Former ) ] +// #[ derive( Debug, PartialEq, the_module::Former ) ] #[ debug ] #[ derive( Debug, PartialEq ) ] -pub struct Command< K > +pub struct Child< K > where K : core::hash::Hash + std::cmp::Eq, { pub name : String, + // #[ container( definition = former::HashMapDefinition ) ] pub properties : collection_tools::HashMap< K, Property< K > >, } -// generated by former -impl< K > Command< K > -where - K : core::hash::Hash + std::cmp::Eq, +// == begin_coercing of generated + +#[ automatically_derived ] +impl< K, > Child< K, > where K : core :: hash :: Hash + std :: cmp :: Eq, { + + #[ inline( always ) ] - pub fn former() -> CommandFormer< K > + pub fn former() -> ChildFormer< K, ChildFormerDefinition< K, (), Child< K, >, former :: ReturnPreformed > > { - CommandFormer::< K >::new() + ChildFormer + :: + < K, ChildFormerDefinition< K, (), Child< K, >, former :: ReturnPreformed > > + :: new( former :: ReturnPreformed ) } +} +#[ derive( Debug ) ] +pub struct ChildFormerDefinitionTypes< K, __Context = (), __Formed = Child< K, >, > +where K : core :: hash :: Hash + std :: cmp :: Eq, +{ + _phantom : core :: marker :: PhantomData< ( K, __Context, __Formed ) >, } -// generated by former -pub struct CommandFormerStorage< K > +impl< K, __Context, __Formed, > :: core :: default :: Default +for ChildFormerDefinitionTypes< K, __Context, __Formed, > where - K : core::hash::Hash + std::cmp::Eq, + K : core :: hash :: Hash + std :: cmp :: Eq, +{ + fn default() -> Self + { + Self + { + _phantom : core :: marker :: PhantomData, + } + } +} + +impl< K, __Context, __Formed, > former :: FormerDefinitionTypes +for ChildFormerDefinitionTypes< K, __Context, __Formed, > +where + K : core :: hash :: Hash + std :: cmp :: Eq, { - name : core::option::Option< String >, - properties : core::option::Option< collection_tools::HashMap< K, Property< K > > >, + type Storage = ChildFormerStorage< K, >; + type Formed = __Formed; + type Context = __Context; } -impl< K > Default for CommandFormerStorage< K > +impl< K, Context, Formed > former::FormerMutator +for ChildFormerDefinitionTypes< K, Context, Formed > where - K : core::hash::Hash + std::cmp::Eq, + K : core :: hash :: Hash + std :: cmp :: Eq, { +} - #[ inline( always ) ] +#[ derive( Debug ) ] +pub struct ChildFormerDefinition +< K, __Context = (), __Formed = Child< K, >, __End = former :: ReturnPreformed, > +where + K : core :: hash :: Hash + std :: cmp :: Eq, +{ + _phantom : core :: marker :: PhantomData< ( K, __Context, __Formed, __End ) >, +} + +impl< K, __Context, __Formed, __End, > :: core :: default :: Default +for ChildFormerDefinition< K, __Context, __Formed, __End, > +where + K : core :: hash :: Hash + std :: cmp :: Eq, +{ fn default() -> Self { Self { - name : None, - properties : None, + _phantom : core :: marker :: PhantomData, } } - } -// generated by former -// #[ derive( Debug, Default ) ] -pub struct CommandFormer< K, Context = Command< K >, End = the_module::ReturnFormed > +impl< K, __Context, __Formed, __End, > former :: FormerDefinition +for ChildFormerDefinition< K, __Context, __Formed, __End, > where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Command< K >, Context >, + __End : former :: FormingEnd< ChildFormerDefinitionTypes< K, __Context, __Formed, > >, + K : core :: hash :: Hash + std :: cmp :: Eq, { - storage : CommandFormerStorage< K >, - context : core::option::Option< Context >, - on_end : core::option::Option< End >, + type Types = ChildFormerDefinitionTypes< K, __Context, __Formed, >; + type End = __End; + type Storage = ChildFormerStorage< K, >; + type Formed = __Formed; + type Context = __Context; } -// generated by former -impl< K, Context, End > -CommandFormer< K, Context, End > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Command< K >, Context >, +// pub type ChildFormerWithClosure< K, __Context, __Formed, > = ChildFormerDefinition< K, __Context, __Formed, former :: FormingEndClosure< ChildFormerDefinitionTypes< K, __Context, __Formed, > > >; + +pub struct ChildFormerStorage< K, > where K : core :: hash :: Hash + std :: cmp :: Eq, { + pub name : :: core :: option :: Option< String >, + + pub properties : :: core :: option :: Option< collection_tools :: HashMap< K, Property< K > > >, +} + +impl< K, > :: core :: default :: Default for ChildFormerStorage< K, > where K : core :: hash :: Hash + std :: cmp :: Eq, +{ #[ inline( always ) ] - fn form( mut self ) -> Command< K > + fn default() -> Self { + Self + { + name : :: core :: option :: Option :: None, + properties : :: core :: option :: Option :: None, + } + } +} - let name = if self.storage.name.is_some() +impl< K, > former :: Storage for ChildFormerStorage< K, > where K : core :: hash :: Hash + std :: cmp :: Eq, +{ + type Preformed = Child< K, >; +} + +impl< K, > former :: StoragePreform for ChildFormerStorage< K, > where K : core :: hash :: Hash + std :: cmp :: Eq, +{ + // type Preformed = Child< K, >; + + fn preform( mut self ) -> Self::Preformed + // fn preform( mut self ) -> < Self as former :: Storage > :: Formed + { + let name = if self.name.is_some() { - self.storage.name.take().unwrap() + self.name.take().unwrap() } else { - let val = Default::default(); - val + { + trait MaybeDefault< T > + { + fn maybe_default( self : & Self ) -> T + { + panic!( "Field 'name' isn't initialized" ) + } + } + impl< T > MaybeDefault< T > for & :: core :: marker :: PhantomData< T > {} + impl< T > MaybeDefault< T > for :: core :: marker :: PhantomData< T > where T : :: core :: default :: Default, + { + fn maybe_default( self : & Self ) -> T { T :: default() } + } + ( & :: core :: marker :: PhantomData :: < String > ).maybe_default() + } }; - let properties = if self.storage.properties.is_some() + let properties = if self.properties.is_some() { - self.storage.properties.take().unwrap() + self.properties.take().unwrap() } else { - let val = Default::default(); - val + { + trait MaybeDefault< T > + { + fn maybe_default( self : & Self ) -> T + { + panic!( "Field 'properties' isn't initialized" ) + } + } + impl< T > MaybeDefault< T > for & :: core :: marker :: PhantomData< T > {} + impl< T > MaybeDefault< T > for :: core :: marker :: PhantomData< T > where T : :: core :: default :: Default, + { + fn maybe_default( self : & Self ) -> T { T :: default() } + } + ( & :: core :: marker :: PhantomData :: < collection_tools :: HashMap< K, Property< K > > > ).maybe_default() + } }; - Command - { - name, - properties, - } + let result = Child :: < K, > { name, properties, }; + return result; + } +} + +pub struct ChildFormer< K, Definition = ChildFormerDefinition< K, (), Child< K, >, former::ReturnPreformed >, > +where + K : core::hash::Hash + std::cmp::Eq, + Definition : former::FormerDefinition< Storage = ChildFormerStorage< K, > >, + // Definition::Types : former::FormerDefinitionTypes< Storage = ChildFormerStorage< K, > > +{ + storage : Definition::Storage, + context : core::option::Option< Definition::Context >, + on_end : core::option::Option< Definition::End >, +} + +#[ automatically_derived ] +impl< K, Definition, > ChildFormer< K, Definition, > +where + K : core::hash::Hash + std::cmp::Eq, + Definition : former::FormerDefinition< Storage = ChildFormerStorage< K, > > + // Definition::Types : former::FormerDefinitionTypes< Storage = ChildFormerStorage< K, > >, +{ + + #[ inline( always ) ] + pub fn perform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + let result = self.form(); + return result; } #[ inline( always ) ] - pub fn perform( self ) -> Command< K > + pub fn new( on_end : Definition::End ) -> Self { - self.form() + Self::begin_coercing( None, None, on_end ) } #[ inline( always ) ] - pub fn new() -> CommandFormer< K > + pub fn new_coercing< IntoEnd >( end : IntoEnd ) -> Self + where IntoEnd : Into< Definition::End > { - CommandFormer::< K >::begin - ( - None, - None, - the_module::ReturnFormed, - ) + Self::begin_coercing( None, None, end ) + } + + #[ inline( always ) ] + pub fn begin( mut storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : < Definition as former::FormerDefinition >::End, ) -> Self + { + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some( on_end ), + } } #[ inline( always ) ] - pub fn begin + pub fn begin_coercing< IntoEnd > ( - storage : core::option::Option< CommandFormerStorage< K > >, - context : core::option::Option< Context >, - on_end : End, - ) -> Self + mut storage : core::option::Option< Definition::Storage >, context : core::option::Option< Definition::Context >, on_end : IntoEnd, + ) + -> Self + where + IntoEnd : ::core::convert::Into< < Definition as former::FormerDefinition >::End > { - // xxx : fix - debug_assert!( storage.is_none() ); + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } Self { - storage : Default::default(), + storage : storage.unwrap(), context : context, - on_end : Some( on_end ), + on_end : ::core::option::Option::Some( ::core::convert::Into::into( on_end ) ), } } - /// Return former of your struct moving container there. Should be called after configuring the container. #[ inline( always ) ] - pub fn end( mut self ) -> Context + pub fn form( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + self.end() + } + + #[ inline( always ) ] + pub fn end( mut self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed { let on_end = self.on_end.take().unwrap(); let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) + former::FormingEnd::< Definition::Types >::call( &on_end, self.storage, context ) } - #[ inline( always ) ] + #[ inline ] pub fn name< Src >( mut self, src : Src ) -> Self - where Src : core::convert::Into< String >, + where Src : ::core::convert::Into< String > { debug_assert!( self.storage.name.is_none() ); - self.storage.name = Some( src.into() ); + self.storage.name = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); self } #[ inline( always ) ] - pub fn properties( mut self ) -> the_module::HashMapSubformer - < - K, - Property< K >, - collection_tools::HashMap< K, Property< K > >, - CommandFormer< K, Context, End >, - impl the_module::FormingEnd< collection_tools::HashMap< K, Property< K > >, Self >, - > - { - let formed = self.storage.properties.take(); - let on_end = | formed : collection_tools::HashMap< K, Property< K > >, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - super_former.storage.properties = Some( formed ); - super_former - }; - the_module::HashMapSubformer::begin( formed, Some( self ), on_end ) + pub fn properties_set< Former2 >( self ) -> Former2 + where Former2 : former::FormerBegin< former::HashMapDefinition< K, Property< K >, Self, Self, ChildFormerPropertiesEnd, > > + { + Former2::former_begin( None, Some( self ), ChildFormerPropertiesEnd ) } + #[ inline( always ) ] + pub fn properties( self ) -> former::ContainerFormer::< ( K, Property< K >, ), former::HashMapDefinition< K, Property< K >, Self, Self, ChildFormerPropertiesEnd > > + { + self.properties_set::< former::ContainerFormer::< ( K, Property< K >, ), former::HashMapDefinition< K, Property< K >, Self, Self, ChildFormerPropertiesEnd > >>() + } +} + +// + +impl< K, Definition, > ChildFormer< K, Definition, > +where + K : core::hash::Hash + std::cmp::Eq, + Definition : former::FormerDefinition< Storage = ChildFormerStorage< K, >, Formed = Child< K, > >, + // Definition::Types : former::FormerDefinitionTypes< Storage = ChildFormerStorage< K, >, Formed = Child< K, > >, + Definition::Storage : former::StoragePreform< Preformed = Child< K, > > +{ + pub fn preform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + former::StoragePreform::preform( self.storage ) + } +} + +#[ allow( non_camel_case_types ) ] +pub struct ChildFormerPropertiesEnd; + +#[ automatically_derived ] +impl< K, Definition, > former::FormingEnd< former::HashMapDefinitionTypes< K, Property< K >, ChildFormer< K, Definition, >, ChildFormer< K, Definition, > >, > +for ChildFormerPropertiesEnd +where + K : core::hash::Hash + std::cmp::Eq, + Definition : former::FormerDefinition< Storage = ChildFormerStorage< K, > >, + // Definition::Types : former::FormerDefinitionTypes< Storage = ChildFormerStorage< K, > >, +{ + #[ inline( always ) ] + fn call( &self, storage : collection_tools::HashMap< K, Property< K > >, super_former : Option< ChildFormer< K, Definition, > >, ) -> ChildFormer< K, Definition, > + { + let mut super_former = super_former.unwrap(); + if let Some( ref mut field ) = super_former.storage.properties + { + former::ContainerAssign::assign( field, storage ); + } + else + { + super_former.storage.properties = Some( storage ); + } + super_former + } } -// == +// == end of generated -include!( "../only_test/parametrized_struct.rs" ); +include!( "./only_test/parametrized_struct.rs" ); diff --git a/module/core/former/tests/inc/former_tests/parametrized_struct_where.rs b/module/core/former/tests/inc/former_tests/parametrized_struct_where.rs index c3b12924b7..063e58be1c 100644 --- a/module/core/former/tests/inc/former_tests/parametrized_struct_where.rs +++ b/module/core/former/tests/inc/former_tests/parametrized_struct_where.rs @@ -23,15 +23,19 @@ impl< Name > Property< Name > } #[ derive( Debug, PartialEq, the_module::Former ) ] -pub struct Command< K > +// #[ derive( Debug, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Child< K > where K : core::hash::Hash + std::cmp::Eq, { pub name : String, - #[ subformer( the_module::HashMapSubformer ) ] + #[ container( definition = former::HashMapDefinition ) ] pub properties : collection_tools::HashMap< K, Property< K > >, } -// == +// == begin_coercing of generated -include!( "../only_test/parametrized_struct.rs" ); +// == end of generated + +include!( "./only_test/parametrized_struct.rs" ); diff --git a/module/core/former/tests/inc/former_tests/string_slice.rs b/module/core/former/tests/inc/former_tests/string_slice.rs index b0c032d9cd..70466144db 100644 --- a/module/core/former/tests/inc/former_tests/string_slice.rs +++ b/module/core/former/tests/inc/former_tests/string_slice.rs @@ -1,11 +1,15 @@ use super::*; -#[ derive( Debug, PartialEq, the_module::Former ) ] +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ derive( Debug, PartialEq, former::Former ) ] #[ debug ] +// #[ derive( Debug, PartialEq ) ] pub struct Struct1< 'a > { pub string_slice_1 : &'a str, } -// +// === begin_coercing of generated -include!( "../only_test/string_slice.rs" ); +// === end of generated + +include!( "./only_test/string_slice.rs" ); diff --git a/module/core/former/tests/inc/former_tests/string_slice_manual.rs b/module/core/former/tests/inc/former_tests/string_slice_manual.rs index 5d3143d25f..6b9632f366 100644 --- a/module/core/former/tests/inc/former_tests/string_slice_manual.rs +++ b/module/core/former/tests/inc/former_tests/string_slice_manual.rs @@ -7,27 +7,111 @@ pub struct Struct1< 'a > pub string_slice_1 : &'a str, } +// === begin_coercing of generated + +#[ automatically_derived ] impl< 'a > Struct1< 'a > { - #[ inline ] + + #[ inline( always ) ] pub fn former() -> Struct1Former< 'a > { - Struct1Former - { - string_slice_1 : ::core::option::Option::None, - } + Struct1Former::new_coercing( former::ReturnPreformed ) } } -pub struct Struct1Former< 'a > +// = definition types + +#[ derive( Debug ) ] +// pub struct Struct1FormerDefinitionTypes< 'a, Context = (), Formed = Struct1< 'a > > +pub struct Struct1FormerDefinitionTypes< 'a, Context, Formed > { - string_slice_1 : ::core::option::Option< &'a str >, + _phantom : core::marker::PhantomData< ( &'a(), Context, Formed ) >, } -impl< 'a > Struct1Former< 'a > +impl< 'a, Context, Formed > Default for Struct1FormerDefinitionTypes< 'a, Context, Formed > { - #[ inline ] - pub fn form( mut self ) -> Struct1< 'a > + fn default() -> Self + { + Self { _phantom : core::marker::PhantomData, } + } +} + +impl< 'a, Context, Formed > former::FormerDefinitionTypes +for Struct1FormerDefinitionTypes< 'a, Context, Formed > +{ + type Storage = Struct1FormerStorage< 'a >; + type Formed = Formed; + type Context = Context; +} + +// = former mutator + +impl< 'a, Context, Formed > former::FormerMutator +for Struct1FormerDefinitionTypes< 'a, Context, Formed > +{ +} + +// = definition + +#[ derive( Debug ) ] +// pub struct Struct1FormerDefinition< 'a, Context = (), Formed = Struct1< 'a >, End = former::ReturnPreformed > +pub struct Struct1FormerDefinition< 'a, Context, Formed, End > +{ + _phantom : core::marker::PhantomData< ( &'a(), Context, Formed, End ) >, +} + +impl< 'a, Context, Formed, End > Default for Struct1FormerDefinition< 'a, Context, Formed, End > +{ + fn default() -> Self + { + Self { _phantom : core::marker::PhantomData, } + } +} + +impl< 'a, Context, Formed, End > former::FormerDefinition +for Struct1FormerDefinition< 'a, Context, Formed, End > +where + End : former::FormingEnd< Struct1FormerDefinitionTypes< 'a, Context, Formed > > +{ + type Types = Struct1FormerDefinitionTypes< 'a, Context, Formed >; + type End = End; + type Storage = Struct1FormerStorage< 'a >; + type Formed = Formed; + type Context = Context; +} + +// pub type Struct1FormerWithClosure< 'a, Context, Formed > = +// Struct1FormerDefinition< 'a, Context, Formed, former::FormingEndClosure< Struct1FormerDefinitionTypes< 'a, Context, Formed > > >; + +// = storage + +pub struct Struct1FormerStorage< 'a > +{ + pub string_slice_1 : ::core::option::Option< &'a str >, +} + +impl< 'a > ::core::default::Default for Struct1FormerStorage< 'a > +{ + #[ inline( always ) ] + fn default() -> Self + { + Self { string_slice_1 : ::core::option::Option::None, } + } +} + +impl< 'a > former::Storage for Struct1FormerStorage< 'a > +{ + type Preformed = Struct1< 'a >; +} + +impl< 'a > former::StoragePreform for Struct1FormerStorage< 'a > +{ + // type Preformed = Struct1< 'a >; + + fn preform( mut self ) -> Self::Preformed + // fn preform( mut self ) -> < Self as former::Storage >::Formed + // fn preform( mut self ) -> Struct1< 'a > { let string_slice_1 = if self.string_slice_1.is_some() { @@ -35,23 +119,150 @@ impl< 'a > Struct1Former< 'a > } else { - let val : &'a str = ::core::default::Default::default(); - val + { + trait MaybeDefault< T > + { + fn maybe_default( self : & Self ) -> T + { + panic!( "Field 'string_slice_1' isn't initialized" ) + } + } + + impl< T > MaybeDefault< T > for & ::core::marker::PhantomData< T > {} + impl< T > MaybeDefault< T > for ::core::marker::PhantomData< T > + where T : ::core::default::Default, + { + fn maybe_default( self : & Self ) -> T { T::default() } + } + + (& ::core::marker::PhantomData::< &'a str >).maybe_default() + } }; - Struct1 { string_slice_1 } + let result = Struct1 { string_slice_1, }; + return result; + } +} + +// = former + +pub struct Struct1Former< 'a, Definition = Struct1FormerDefinition< 'a, (), Struct1< 'a >, former::ReturnPreformed > > +where + // End : former::FormingEnd::< Definition::Types >, + // Definition : former::FormerDefinition< End = End >, + // Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage< 'a >, Formed = Formed, Context = Context >, + Definition : former::FormerDefinition, + Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage< 'a > >, +{ + storage : Definition::Storage, + context : core::option::Option< Definition::Context >, + on_end : core::option::Option< Definition::End >, +} + +#[ automatically_derived ] +impl< 'a, Definition > Struct1Former< 'a, Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage< 'a > >, + // Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage< 'a > >, +{ + + #[ inline( always ) ] + pub fn perform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + let result = self.form(); + return result; + } + + #[ inline( always ) ] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin_coercing( None, None, on_end ) + } + + #[ inline( always ) ] + pub fn new_coercing< IntoEnd >( end : IntoEnd ) -> Self + where IntoEnd : Into< Definition::End >, + { + Self::begin_coercing( None, None, end, ) + } + + #[ inline( always ) ] + pub fn begin + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : < Definition as former::FormerDefinition >::End, + ) -> Self + { + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some( on_end ), + } + } + + #[ inline( always ) ] + pub fn begin_coercing< IntoEnd > + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : IntoEnd, + ) -> Self + where IntoEnd : ::core::convert::Into< < Definition as former::FormerDefinition >::End >, + { + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some( ::core::convert::Into::into( on_end ) ), + } + } + + #[ inline( always ) ] + pub fn form( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + self.end() + } + + #[ inline( always ) ] + pub fn end( mut self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let context = self.context.take(); + former::FormingEnd::< Definition::Types >::call( & on_end, self.storage, context ) } #[ inline ] pub fn string_slice_1< Src >( mut self, src : Src ) -> Self - where - Src : ::core::convert::Into< &'a str >, + where Src : ::core::convert::Into< &'a str >, { - debug_assert!( self.string_slice_1.is_none() ); - self.string_slice_1 = ::core::option::Option::Some( src.into() ); + debug_assert!( self.storage.string_slice_1.is_none() ); + self.storage.string_slice_1 = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); self } } -// +impl< 'a, Definition > Struct1Former< 'a, Definition > +where + Definition : former::FormerDefinition< Storage = Struct1FormerStorage< 'a >, Formed = Struct1< 'a > >, + // Definition::Types : former::FormerDefinitionTypes< Storage = Struct1FormerStorage< 'a >, Formed = Struct1< 'a > >, + Definition::Storage : former::StoragePreform< Preformed = Struct1< 'a > >, +{ + pub fn preform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + // panic!(); + former::StoragePreform::preform( self.storage ) + } +} + +// === end of generated -include!( "../only_test/string_slice.rs" ); +include!( "./only_test/string_slice.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_basic.rs b/module/core/former/tests/inc/former_tests/subformer_basic.rs index e847a543df..11f5a65779 100644 --- a/module/core/former/tests/inc/former_tests/subformer_basic.rs +++ b/module/core/former/tests/inc/former_tests/subformer_basic.rs @@ -1,10 +1,11 @@ +#![ deny( missing_docs ) ] #![ allow( dead_code ) ] use super::*; // // this should work // -// let ca = Aggregator::former() +// let ca = Parent::former() // .parameter1( "val" ) // .command( "echo" ) // .name( "prints all subjects and properties" ) @@ -47,22 +48,22 @@ impl< Name > Property< Name > // == command #[ derive( Debug, PartialEq, the_module::Former ) ] -pub struct Command< K > +pub struct Child< K > where K : core::hash::Hash + std::cmp::Eq, { pub name : String, pub subject : String, - #[ subformer( the_module::HashMapSubformer ) ] + #[ container( definition = former::HashMapDefinition ) ] pub properties : collection_tools::HashMap< K, Property< K > >, } // manual -impl< K, Context, End > -CommandFormer< K, Context, End > +impl< K, Definition > ChildFormer< K, Definition > where K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Command< K >, Context >, + Definition : former::FormerDefinition< Storage = ChildFormerStorage< K > >, + Definition::Storage : former::StoragePreform, { /// Inserts a key-value pair into the map. Make a new container if it was not made so far. @@ -96,51 +97,15 @@ where // == aggregator #[ derive( Debug, PartialEq, the_module::Former ) ] -pub struct Aggregator< K > +pub struct Parent< K > where K : core::hash::Hash + std::cmp::Eq, { pub parameter1 : String, - #[ subformer( the_module::HashMapSubformer ) ] - pub commands : collection_tools::HashMap< String, Command< K > >, -} - -// manual -impl< K, Context, End > -AggregatorFormer< K, Context, End > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Aggregator< K >, Context >, -{ - - #[ inline( always ) ] - pub fn command< IntoName >( self, name : IntoName ) - -> CommandFormer< K, Self, impl the_module::FormingEnd< Command< K >, Self > > - where - K : core::hash::Hash + std::cmp::Eq, - IntoName : core::convert::Into< String >, - { - let on_end = | command : Command< K >, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if let Some( ref mut commands ) = super_former.storage.commands - { - commands.insert( command.name.clone(), command ); - } - else - { - let mut commands : collection_tools::HashMap< String, Command< K > > = Default::default(); - commands.insert( command.name.clone(), command ); - super_former.storage.commands = Some( commands ); - } - super_former - }; - let former = CommandFormer::begin( None, Some( self ), on_end ); - former.name( name ) - } - + #[ container( definition = former::HashMapDefinition ) ] + pub commands : collection_tools::HashMap< String, Child< K > >, } // == -include!( "../only_test/subformer_basic.rs" ); +include!( "./only_test/subformer_basic.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_basic_manual.rs b/module/core/former/tests/inc/former_tests/subformer_basic_manual.rs deleted file mode 100644 index cbb94f6e73..0000000000 --- a/module/core/former/tests/inc/former_tests/subformer_basic_manual.rs +++ /dev/null @@ -1,465 +0,0 @@ -#![ allow( dead_code ) ] -use super::*; - -// let ca = Aggregator::former() -// .parameter1( "val" ) -// .command( "echo" ) -// .name( "prints all subjects and properties" ) -// .subject( "Subject", wca::Type::String, true ) -// .property( "property", "simple property", wca::Type::String, true ) -// .routine( f1 ) -// .end() -// .command( "exit" ) -// .name( "just exit" ) -// .routine( || exit() ) -// .end() -// .perform() -// ; -// ca.execute( input ).unwrap(); - -#[ derive( Debug, PartialEq, Default ) ] -pub struct Property< Name > -{ - name : Name, - description : String, - code : isize, -} - -/// generated by new -impl< Name > Property< Name > -{ - #[ inline ] - pub fn new< Description, Code >( name : Name, description : Description, code : Code ) -> Self - where - Name : core::convert::Into< Name >, - Description : core::convert::Into< String >, - Code : core::convert::Into< isize >, - { - Self { name : name.into(), description : description.into(), code : code.into() } - } -} - -#[ derive( Debug, PartialEq ) ] -pub struct Command< K > -where - K : core::hash::Hash + std::cmp::Eq, -{ - pub name : String, - pub subject : String, - pub properties : collection_tools::HashMap< K, Property< K > >, -} - -// generated by former -impl< K > Command< K > -where - K : core::hash::Hash + std::cmp::Eq, -{ - - #[ inline( always ) ] - pub fn former() -> CommandFormer< K > - { - CommandFormer::< K >::new() - } - -} - -// generated by former -pub struct CommandFormerStorage< K > -where - K : core::hash::Hash + std::cmp::Eq, -{ - name : core::option::Option< String >, - subject : core::option::Option< String >, - properties : core::option::Option< collection_tools::HashMap< K, Property< K > > >, -} - -impl< K > Default for CommandFormerStorage< K > -where - K : core::hash::Hash + std::cmp::Eq, -{ - - #[ inline( always ) ] - fn default() -> Self - { - Self - { - name : None, - subject : None, - properties : None, - } - } - -} - -// generated by former -// #[ derive( Debug, Default ) ] -pub struct CommandFormer< K, Context = Command< K >, End = the_module::ReturnFormed > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Command< K >, Context >, -{ - storage : CommandFormerStorage< K >, - context : core::option::Option< Context >, - on_end : core::option::Option< End >, -} - -// generated by former -impl< K, Context, End > -CommandFormer< K, Context, End > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Command< K >, Context >, -{ - - #[ inline( always ) ] - fn form( mut self ) -> Command< K > - { - - let name = if self.storage.name.is_some() - { - self.storage.name.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - - let subject = if self.storage.subject.is_some() - { - self.storage.subject.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - - let properties = if self.storage.properties.is_some() - { - self.storage.properties.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - - Command - { - name, - subject, - properties, - } - } - - #[ inline( always ) ] - pub fn perform( self ) -> Command< K > - { - self.form() - } - - #[ inline( always ) ] - pub fn new() -> CommandFormer< K > - { - CommandFormer::< K >::begin - ( - None, - None, - the_module::ReturnFormed, - ) - } - - #[ inline( always ) ] - pub fn begin - ( - storage : core::option::Option< CommandFormerStorage< K > >, - context : core::option::Option< Context >, - on_end : End, - ) -> Self - { - // qqq : fix - debug_assert!( storage.is_none() ); - Self - { - storage : Default::default(), - context : context, - on_end : Some( on_end ), - } - } - - /// Return former of your struct moving container there. Should be called after configuring the container. - #[ inline( always ) ] - pub fn end( mut self ) -> Context - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - #[ inline( always ) ] - pub fn name< Src >( mut self, src : Src ) -> Self - where Src : core::convert::Into< String >, - { - debug_assert!( self.storage.name.is_none() ); - self.storage.name = Some( src.into() ); - self - } - - #[ inline( always ) ] - pub fn subject< Src >( mut self, src : Src ) -> Self - where Src : core::convert::Into< String >, - { - debug_assert!( self.storage.subject.is_none() ); - self.storage.subject = Some( src.into() ); - self - } - - #[ inline( always ) ] - pub fn properties( mut self ) -> the_module::HashMapSubformer - < - K, - Property< K >, - collection_tools::HashMap< K, Property< K > >, - CommandFormer< K, Context, End >, - impl the_module::FormingEnd< collection_tools::HashMap< K, Property< K > >, Self >, - > - { - let formed = self.storage.properties.take(); - let on_end = | formed : collection_tools::HashMap< K, Property< K > >, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - super_former.storage.properties = Some( formed ); - super_former - }; - the_module::HashMapSubformer::begin( formed, Some( self ), on_end ) - } - -} - -// manual -impl< K, Context, End > -CommandFormer< K, Context, End > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Command< K >, Context >, -{ - - /// Inserts a key-value pair into the map. Make a new container if it was not made so far. - #[ inline( always ) ] - pub fn property< Name, Description, Code > - ( mut self, name : Name, description : Description, code : Code ) -> Self - where - Name : core::convert::Into< K > + Clone, - Description : core::convert::Into< String >, - Code : core::convert::Into< isize >, - { - if self.storage.properties.is_none() - { - self.storage.properties = core::option::Option::Some( Default::default() ); - } - if let core::option::Option::Some( ref mut properties ) = self.storage.properties - { - let property = Property - { - name : name.clone().into(), - description : description.into(), - code : code.into(), - }; - properties.insert( name.into(), property ); - } - self - } - -} - -// == aggregator - -#[ derive( Debug, PartialEq ) ] -pub struct Aggregator< K > -where - K : core::hash::Hash + std::cmp::Eq, -{ - pub parameter1 : String, - pub commands : collection_tools::HashMap< String, Command< K > >, -} - -// generated by former -impl< K > Aggregator< K > -where - K : core::hash::Hash + std::cmp::Eq, -{ - - #[ inline( always ) ] - pub fn former() -> AggregatorFormer< K > - { - AggregatorFormer::< K >::new() - } - -} - -// generated by former -// #[ derive( Debug, Default ) ] -pub struct AggregatorFormer< K, Context = Aggregator< K >, End = the_module::ReturnFormed > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Aggregator< K >, Context >, -{ - parameter1 : core::option::Option< String >, - commands : core::option::Option< collection_tools::HashMap< String, Command< K > > >, - context : core::option::Option< Context >, - on_end : core::option::Option< End >, -} - -// generated by former -impl< K, Context, End > -AggregatorFormer< K, Context, End > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Aggregator< K >, Context >, -{ - - #[ inline( always ) ] - fn form( mut self ) -> Aggregator< K > - { - - let parameter1 = if self.parameter1.is_some() - { - self.parameter1.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - - let commands = if self.commands.is_some() - { - self.commands.take().unwrap() - } - else - { - let val = Default::default(); - val - }; - - Aggregator - { - parameter1, - commands, - } - } - - #[ inline( always ) ] - pub fn perform( self ) -> Aggregator< K > - { - self.form() - } - - #[ inline( always ) ] - pub fn new() -> AggregatorFormer< K > - { - AggregatorFormer::< K >::begin - ( - None, - the_module::ReturnFormed, - ) - } - - #[ inline( always ) ] - pub fn begin - ( - context : core::option::Option< Context >, - on_end : End, - ) -> Self - { - Self - { - parameter1 : None, - commands : None, - context : context, - on_end : Some( on_end ), - } - } - - /// Return former of your struct moving container there. Should be called after configuring the container. - #[ inline( always ) ] - pub fn end( mut self ) -> Context - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let formed = self.form(); - on_end.call( formed, context ) - } - - #[ inline( always ) ] - pub fn parameter1< Src >( mut self, src : Src ) -> Self - where Src : core::convert::Into< String >, - { - debug_assert!( self.parameter1.is_none() ); - self.parameter1 = Some( src.into() ); - self - } - - #[ inline( always ) ] - pub fn commands( mut self ) -> the_module::HashMapSubformer - < - String, - Command< K >, - collection_tools::HashMap< String, Command< K > >, - AggregatorFormer< K, Context, End >, - impl the_module::FormingEnd< collection_tools::HashMap< String, Command< K > >, Self >, - > - { - let formed = self.commands.take(); - let on_end = | formed : collection_tools::HashMap< String, Command< K > >, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - super_former.commands = Some( formed ); - super_former - }; - the_module::HashMapSubformer::begin( formed, Some( self ), on_end ) - } - -} - -// manual -impl< K, Context, End > -AggregatorFormer< K, Context, End > -where - K : core::hash::Hash + std::cmp::Eq, - End : the_module::FormingEnd< Aggregator< K >, Context >, -{ - - #[ inline( always ) ] - pub fn command< IntoName >( self, name : IntoName ) - -> CommandFormer< K, Self, impl the_module::FormingEnd< Command< K >, Self > > - where - K : core::hash::Hash + std::cmp::Eq, - IntoName : core::convert::Into< String >, - { - let on_end = | command : Command< K >, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if let Some( ref mut commands ) = super_former.commands - { - commands.insert( command.name.clone(), command ); - } - else - { - let mut commands : collection_tools::HashMap< String, Command< K > > = Default::default(); - commands.insert( command.name.clone(), command ); - super_former.commands = Some( commands ); - } - super_former - }; - let former = CommandFormer::begin( None, Some( self ), on_end ); - former.name( name ) - } - -} - -// == - -include!( "../only_test/subformer_basic.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_container.rs b/module/core/former/tests/inc/former_tests/subformer_container.rs new file mode 100644 index 0000000000..c168f83e8b --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_container.rs @@ -0,0 +1,27 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ container( definition = former::VectorDefinition ) ] + children : Vec< Child >, +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/subformer_container.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_container_custom.rs b/module/core/former/tests/inc/former_tests/subformer_container_custom.rs new file mode 100644 index 0000000000..58f3a72356 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_container_custom.rs @@ -0,0 +1,264 @@ +#![ deny( missing_docs ) ] +#![ allow( dead_code ) ] + +use super::*; +use collection_tools::HashSet; + +// == define custom containers + +// Custom container that logs additions +#[ derive( Debug, PartialEq ) ] +pub struct LoggingSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + set : HashSet< K >, +} + +impl< K > Default for LoggingSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + + #[ inline( always ) ] + fn default() -> Self + { + Self + { + set : Default::default() + } + } + +} + +impl< K > IntoIterator for LoggingSet< K > +where + K : std::cmp::Eq + std::hash::Hash, +{ + type Item = K; + type IntoIter = std::collections::hash_set::IntoIter< K >; + + fn into_iter( self ) -> Self::IntoIter + { + self.set.into_iter() + } +} + +impl<'a, K> IntoIterator for &'a LoggingSet< K > +where + K : std::cmp::Eq + std::hash::Hash, +{ + type Item = &'a K; + type IntoIter = std::collections::hash_set::Iter< 'a, K >; + + fn into_iter( self ) -> Self::IntoIter + { + self.set.iter() + } +} + +impl< K > former::Container for LoggingSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + type Entry = K; + type Val = K; + + #[ inline( always ) ] + fn entry_to_val( e : Self::Entry ) -> Self::Val + { + e + } + +} + +impl< K > former::ContainerAdd for LoggingSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + + #[ inline( always ) ] + fn add( &mut self, e : Self::Entry ) -> bool + { + self.set.insert( e ) + } + +} + +impl< K > former::ContainerAssign for LoggingSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + fn assign< Elements >( &mut self, elements : Elements ) -> usize + where + Elements : IntoIterator< Item = Self::Entry > + { + let initial_len = self.set.len(); + self.set.extend( elements ); + self.set.len() - initial_len + } +} + +impl< K > former::ContainerValToEntry< K > for LoggingSet< K > +where + K : core::cmp::Eq + core::hash::Hash, +{ + type Entry = K; + #[ inline( always ) ] + fn val_to_entry( val : K ) -> Self::Entry + { + val + } +} + +// = storage + +impl< K > former::Storage +for LoggingSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Preformed = LoggingSet< K >; +} + +impl< K > former::StoragePreform +for LoggingSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + fn preform( self ) -> Self::Preformed + { + self + } +} + +// = definition types + +#[ derive( Debug, Default ) ] +pub struct LoggingSetDefinitionTypes< K, Context = (), Formed = LoggingSet< K > > +{ + _phantom : core::marker::PhantomData< ( K, Context, Formed ) >, +} + +impl< K, Context, Formed > former::FormerDefinitionTypes +for LoggingSetDefinitionTypes< K, Context, Formed > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Storage = LoggingSet< K >; + type Formed = Formed; + type Context = Context; +} + +// = definition + +#[ derive( Debug, Default ) ] +pub struct LoggingSetDefinition< K, Context = (), Formed = LoggingSet< K >, End = former::ReturnStorage > +{ + _phantom : core::marker::PhantomData< ( K, Context, Formed, End ) >, +} + +impl< K, Context, Formed, End > former::FormerDefinition +for LoggingSetDefinition< K, Context, Formed, End > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : former::FormingEnd< LoggingSetDefinitionTypes< K, Context, Formed > >, +{ + type Storage = LoggingSet< K >; + type Formed = Formed; + type Context = Context; + + type Types = LoggingSetDefinitionTypes< K, Context, Formed >; + type End = End; +} + +// = mutator + +impl< K, Context, Formed > former::FormerMutator +for LoggingSetDefinitionTypes< K, Context, Formed > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ +} + +// = Entity To + +impl< K, Definition > former::EntityToFormer< Definition > for LoggingSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + Definition : former::FormerDefinition + < + Storage = LoggingSet< K >, + Types = LoggingSetDefinitionTypes + < + K, + < Definition as former::FormerDefinition >::Context, + < Definition as former::FormerDefinition >::Formed, + >, + >, + Definition::End : former::FormingEnd< Definition::Types >, +{ + type Former = LoggingSetAsSubformer< K, Definition::Context, Definition::Formed, Definition::End >; +} + +impl< K > former::EntityToStorage +for LoggingSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Storage = LoggingSet< K >; +} + +impl< K, Context, Formed, End > former::EntityToDefinition< Context, Formed, End > +for LoggingSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, + End : former::FormingEnd< LoggingSetDefinitionTypes< K, Context, Formed > >, +{ + type Definition = LoggingSetDefinition< K, Context, Formed, End >; + type Types = LoggingSetDefinitionTypes< K, Context, Formed >; +} + +impl< K, Context, Formed > former::EntityToDefinitionTypes< Context, Formed > +for LoggingSet< K > +where + K : ::core::cmp::Eq + ::core::hash::Hash, +{ + type Types = LoggingSetDefinitionTypes< K, Context, Formed >; +} + +// = subformer + +pub type LoggingSetAsSubformer< K, Context, Formed, End > = +former::ContainerFormer::< K, LoggingSetDefinition< K, Context, Formed, End > >; + +// == use custom container + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Parent +{ + #[ container ] + children : LoggingSet< i32 >, +} + +// == begin of generated + +// == end of generated + +#[ test ] +fn basic() +{ + + // Using the builder pattern provided by Former to manipulate Parent + let parent = Parent::former() + .children() + .add(10) + .add(20) + .add(10) + .end() + .form(); + + println!("Got: {:?}", parent); + +} diff --git a/module/core/former/tests/inc/former_tests/subformer_container_implicit.rs b/module/core/former/tests/inc/former_tests/subformer_container_implicit.rs new file mode 100644 index 0000000000..55b91e69de --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_container_implicit.rs @@ -0,0 +1,29 @@ +#![ deny( missing_docs ) ] +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + // #[ container( definition = former::VectorDefinition ) ] + #[ container ] + children : Vec< Child >, +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/subformer_container.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_container_manual.rs b/module/core/former/tests/inc/former_tests/subformer_container_manual.rs new file mode 100644 index 0000000000..82811c38eb --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_container_manual.rs @@ -0,0 +1,109 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + // #[ container( definition = former::VectorDefinition ) ] + #[ scalar( setter = false ) ] + children : Vec< Child >, +} + +// == begin of generated for Parent in context of attribute container( former::VectorDefinition ) ] + +#[ automatically_derived ] +impl< Definition, > ParentFormer< Definition, > +where + Definition : former::FormerDefinition< Storage = ParentFormerStorage< > >, +{ + + #[ inline( always ) ] + pub fn _children_container_former< Former2 >( self ) -> Former2 + where + Former2 : former::FormerBegin< former::VectorDefinition< Child, Self, Self, ParentFormerAssignChildrenEnd< Definition >, > >, + { + Former2::former_begin( None, Some( self ), ParentFormerAssignChildrenEnd::< Definition >::default() ) + } + + #[ inline( always ) ] + pub fn children( self ) -> former::ContainerFormer:: + < + Child, + former::VectorDefinition< Child, Self, Self, ParentFormerAssignChildrenEnd< Definition >, > + > + { + self._children_container_former::< former::ContainerFormer::< Child, former::VectorDefinition< Child, Self, Self, ParentFormerAssignChildrenEnd< Definition >, > > >() + } + +} + +// + +#[ doc = r"Callback to return original former after forming of container for `vec_1` is done. Callback replace content of container assigning new content from subformer's storage." ] +pub struct ParentFormerAssignChildrenEnd< Definition > +{ + _phantom : core::marker::PhantomData< ( Definition, ) >, +} + +impl< Definition > Default for ParentFormerAssignChildrenEnd< Definition > +{ + + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + +} + +#[ automatically_derived ] +impl< Definition, > former::FormingEnd +< + < + Vec< Child > as former::EntityToDefinitionTypes< ParentFormer< Definition, >, ParentFormer< Definition, > > + >::Types +> +for ParentFormerAssignChildrenEnd< Definition > +where + Definition : former::FormerDefinition< Storage = ParentFormerStorage< > >, +{ + #[ inline( always ) ] + fn call + ( + &self, + storage : Vec< Child >, + super_former : Option< ParentFormer< Definition, > >, + ) + -> ParentFormer< Definition, > + { + let mut super_former = super_former.unwrap(); + if let Some( ref mut field ) = super_former.storage.children + { + former::ContainerAssign::assign( field, storage ); + } + else + { + super_former.storage.children = Some( storage ); + } + super_former + } +} + +// == end of generated for Parent in context of attribute container( former::VectorDefinition ) ] + +include!( "./only_test/subformer_container.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_container_named.rs b/module/core/former/tests/inc/former_tests/subformer_container_named.rs new file mode 100644 index 0000000000..75ce8845b6 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_container_named.rs @@ -0,0 +1,43 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ container( name = children2 ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn children( self ) -> &'static str + { + r#" + Scalar setter `children` should not be generated by default if container is used. + It can only be generated if req + "# + } + +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/subformer_container_children2.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_container_setter_off.rs b/module/core/former/tests/inc/former_tests/subformer_container_setter_off.rs new file mode 100644 index 0000000000..5f3ca56708 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_container_setter_off.rs @@ -0,0 +1,51 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Child +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent + +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ container( setter = false ) ] + // #[ scalar( setter = false ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn children( self ) -> &'static str + { + r#" + Scalar setter `children` should not be generated by default if container is used. + It can only be generated if req + "# + } + + #[ inline( always ) ] + pub fn children2( self ) -> former::ContainerFormer:: + < + Child, + former::VectorDefinition< Child, Self, Self, ParentFormerAssignChildrenEnd< Definition >, > + > + { + self._children_container_former::< _ >() + } + +} + +include!( "./only_test/subformer_container_children2.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_container_setter_on.rs b/module/core/former/tests/inc/former_tests/subformer_container_setter_on.rs new file mode 100644 index 0000000000..83233366cf --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_container_setter_on.rs @@ -0,0 +1,45 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Child +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent + +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + // Such parameters switch off generation of front-end container setter and switch on scalar setter. + // Without explicit scalar_setter( true ) scalar setter is not generated. + #[ subform( setter = false ) ] + #[ scalar( setter = true ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn children2( self ) -> former::ContainerFormer:: + < + Child, + former::VectorDefinition< Child, Self, Self, ParentFormerAssignChildrenEnd< Definition >, > + > + { + self._children_container_former::< _ >() + } + +} + +include!( "./only_test/subformer_scalar_children.rs" ); +include!( "./only_test/subformer_container_children2.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_hashmap.rs b/module/core/former/tests/inc/former_tests/subformer_hashmap.rs deleted file mode 100644 index cfe3574aed..0000000000 --- a/module/core/former/tests/inc/former_tests/subformer_hashmap.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![ allow( dead_code ) ] - -#[ allow( unused_imports ) ] -use super::*; -#[ allow( unused_imports ) ] -use collection_tools::HashMap; - -// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] -#[ cfg( not( feature = "use_alloc" ) ) ] -#[ test ] -fn push() -{ - - // - - let got : HashMap< String, String > = the_module::HashMapSubformer::new() - .insert( "a", "x" ) - .insert( "b", "y" ) - .form(); - let exp = hmap! - [ - "a".to_string() => "x".to_string(), - "b".to_string() => "y".to_string(), - ]; - a_id!( got, exp ); - -} - -// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] -#[ cfg( not( feature = "use_alloc" ) ) ] -#[ test ] -fn replace() -{ - - let got : HashMap< String, String > = the_module::HashMapSubformer::new() - .insert( "x", "x" ) - .replace( hmap![ "a".to_string() => "x".to_string(), "b".to_string() => "y".to_string() ] ) - .form(); - let exp = hmap! - [ - "a".to_string() => "x".to_string(), - "b".to_string() => "y".to_string(), - ]; - a_id!( got, exp ); - -} - diff --git a/module/core/former/tests/inc/former_tests/subformer_hashset.rs b/module/core/former/tests/inc/former_tests/subformer_hashset.rs deleted file mode 100644 index eeca2ca81c..0000000000 --- a/module/core/former/tests/inc/former_tests/subformer_hashset.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![ allow( dead_code ) ] - -#[ allow( unused_imports ) ] -use super::*; -#[ allow( unused_imports ) ] -use collection_tools::HashSet; - - -// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] -#[ cfg( not( feature = "use_alloc" ) ) ] -#[ test ] -fn push() -{ - - let got : HashSet< String > = the_module::HashSetSubformer::new() - .insert( "a" ) - .insert( "b" ) - .form(); - let exp = hset! - [ - "a".to_string(), - "b".to_string(), - ]; - a_id!( got, exp ); - -} - -// qqq : zzz : remove #[ cfg( not( feature = "use_alloc" ) ) ] -#[ cfg( not( feature = "use_alloc" ) ) ] -#[ test ] -fn replace() -{ - - let got : HashSet< String > = the_module::HashSetSubformer::new() - .insert( "x" ) - .replace( hset![ "a".to_string(), "b".to_string() ] ) - .form(); - let exp = hset! - [ - "a".to_string(), - "b".to_string(), - ]; - a_id!( got, exp ); - -} diff --git a/module/core/former/tests/inc/former_tests/subformer_shortcut.rs b/module/core/former/tests/inc/former_tests/subformer_shortcut.rs deleted file mode 100644 index bf8c825690..0000000000 --- a/module/core/former/tests/inc/former_tests/subformer_shortcut.rs +++ /dev/null @@ -1,114 +0,0 @@ -#![ allow( dead_code ) ] - -use super::*; - -/// Parameter description. -#[ derive( Debug, Default, PartialEq, the_module::Former ) ] -pub struct TemplateParameterDescriptor -{ - descriptor : String, - is_mandatory : bool, -} - -/// Parameters required for the template. -#[ derive( Debug, Default, PartialEq, the_module::Former ) ] -pub struct TemplateParameters -{ - // #[ debug = the_module::VectorSubformer, descriptor, descriptor( name ) ] - #[ subformer( the_module::VectorSubformer ) ] - descriptors : Vec< TemplateParameterDescriptor >, - - // #[ subformer_setter = the_module::VectorSubformer ] - // pub fn descriptor( self, name : &str ) - // { - // descriptor( name ) - // } - -} - -impl< Context, End > former::FormerBegin< TemplateParameterDescriptorFormerStorage, TemplateParameterDescriptor, Context > -for TemplateParameterDescriptorFormer< Context, End > -where - End : the_module::FormingEnd< TemplateParameterDescriptor, Context >, -{ - type End = End; - - #[ inline( always ) ] - fn _begin - ( - storage : core::option::Option< TemplateParameterDescriptorFormerStorage >, /* xxx2 : that should be storage */ - context : core::option::Option< Context >, - on_end : End, - ) -> Self - { - debug_assert!( storage.is_none() ); - Self::begin( None, context, on_end ) - } - -} - -impl< Context, End > TemplateParametersFormer< Context, End > -where - End : former::FormingEnd< TemplateParameters, Context >, -{ - - #[ inline( always ) ] - pub fn descriptor3< Former2 >( self ) -> - Former2 - where - Former2 : former::FormerBegin - < - TemplateParameterDescriptorFormerStorage, - TemplateParameterDescriptor, - Self, - End = former::FormingEndWrapper< TemplateParameterDescriptor, Self >, - >, - // FieldContainer : ContainerAdd, - { - let on_end = | descriptor : TemplateParameterDescriptor, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if super_former.storage.descriptors.is_none() - { - super_former.storage.descriptors = Some( Default::default() ); - } - if let Some( ref mut descriptors ) = super_former.storage.descriptors - { - former::ContainerAdd::add( descriptors, descriptor ); - } - super_former - }; - Former2::_begin( None, Some( self ), former::FormingEndWrapper::new( on_end ) ) - } - - // xxx2 : move to a trait and make easier to use subformer, trait with generic interface of a container should help - - #[ inline( always ) ] - pub fn descriptor( self, name : &str ) -> - TemplateParameterDescriptorFormer< Self, impl former::FormingEnd< TemplateParameterDescriptor, Self > > - { - self.descriptor3::< TemplateParameterDescriptorFormer< _, _ > >().descriptor( name ) - } - -} - -#[ test ] -fn basic() -{ - - let got = TemplateParameters::former() - .descriptors() - .push( TemplateParameterDescriptor::former().descriptor( "a" ).form() ) - .push( TemplateParameterDescriptor::former().descriptor( "b" ).form() ) - .end() - .form(); - - let descriptors = vec! - [ - TemplateParameterDescriptor { descriptor : "a".to_string(), is_mandatory : false }, - TemplateParameterDescriptor { descriptor : "b".to_string(), is_mandatory : false }, - ]; - let exp = TemplateParameters { descriptors }; - a_id!( got, exp ); - -} diff --git a/module/core/former/tests/inc/former_tests/subformer_subform.rs b/module/core/former/tests/inc/former_tests/subformer_subform.rs new file mode 100644 index 0000000000..e112d38ecd --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform.rs @@ -0,0 +1,49 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Child +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent + +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ subform( setter = false ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn child( self, name : &str ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add::< ChildFormer< _ >, _, >() + .name( name ) + } + + #[ inline( always ) ] + pub fn _child( self ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< < Child as former::EntityToFormer< _ > >::Former, _, >() + } + +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/subformer_subform_child.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_and_container.rs b/module/core/former/tests/inc/former_tests/subformer_subform_and_container.rs new file mode 100644 index 0000000000..6a3546113e --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_and_container.rs @@ -0,0 +1,56 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ subform( name = _child ) ] + #[ container( name = children2 ) ] + #[ scalar( name = children3 ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn child( self, name : &str ) -> + ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< ChildFormer< _ >, _, >() + .name( name ) + } + + #[ inline( always ) ] + pub fn children( self ) -> &'static str + { + r#" + Scalar setter `children` should not be generated by default if subform is used. + It can only be generated if req + "# + } + +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/subformer_subform_child.rs" ); +include!( "./only_test/subformer_container_children2.rs" ); +include!( "./only_test/subformer_scalar_children3.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_and_container_parametrized.rs b/module/core/former/tests/inc/former_tests/subformer_subform_and_container_parametrized.rs new file mode 100644 index 0000000000..43347fc9ce --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_and_container_parametrized.rs @@ -0,0 +1,134 @@ +#![ allow( dead_code ) ] +#[ allow( unused_imports ) ] +use super::*; + +/// Parameter description. +#[ allow( explicit_outlives_requirements ) ] +#[ derive( Debug, PartialEq, the_module::Former ) ] +// #[ derive( Debug, PartialEq ) ] +pub struct Child< 'child, T > +where + T : 'child + ?Sized, +{ + name : String, + data : &'child T, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent< 'child > +{ + #[ subform( name = _child ) ] + #[ container( name = children2 ) ] + #[ scalar( name = children3 ) ] + children : Vec< Child< 'child, str > >, +} + +impl< 'child, Definition > ParentFormer< 'child, Definition > +where + Definition : former::FormerDefinition< Storage = < Parent< 'child > as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn child( self, name : &str ) -> + ChildAsSubformer< 'child, str, Self, impl ChildAsSubformerEnd< 'child, str, Self > > + { + self._children_add + ::< ChildFormer< '_, _, _ >, _, >() + .name( name ) + } + +} + +// == begin of generated + +// == end of generated + +#[ test ] +fn subform_child() +{ + + let got = Parent::former() + .child( "a" ).data( "aa" ).end() + .child( "b" ).data( "bb" ).end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : "aa" }, + Child { name : "b".to_string(), data : "bb" }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} + +#[ test ] +fn subform_child_generated() +{ + + let got = Parent::former() + ._child().name( "a" ).data( "aa" ).end() + ._child().name( "b" ).data( "bb" ).end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : "aa" }, + Child { name : "b".to_string(), data : "bb" }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} + +#[ test ] +fn container() +{ + + let got = Parent::former() + .children2() + .add( Child::former().name( "a" ).data( "aa" ).form() ) + .add( Child::former().name( "b" ).data( "bb" ).form() ) + .end() + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : "aa" }, + Child { name : "b".to_string(), data : "bb" }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} + + +#[ test ] +fn scalar() +{ + + let children = vec! + [ + Child { name : "a".to_string(), data : "aa" }, + Child { name : "b".to_string(), data : "bb" }, + ]; + let got = Parent::former() + .children3( children ) + .form(); + + let children = vec! + [ + Child { name : "a".to_string(), data : "aa" }, + Child { name : "b".to_string(), data : "bb" }, + ]; + let exp = Parent { children }; + a_id!( got, exp ); + +} + +// include!( "./only_test/subformer_subform_child.rs" ); +// include!( "./only_test/subformer_container_children2.rs" ); +// include!( "./only_test/subformer_scalar_children3.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_and_container_private.rs b/module/core/former/tests/inc/former_tests/subformer_subform_and_container_private.rs new file mode 100644 index 0000000000..1c915adce1 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_and_container_private.rs @@ -0,0 +1,56 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +struct Parent +{ + #[ subform( name = _child ) ] + #[ container( name = children2 ) ] + #[ scalar( name = children3 ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + fn child( self, name : &str ) -> + ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< ChildFormer< _ >, _, >() + .name( name ) + } + + #[ inline( always ) ] + fn children( self ) -> &'static str + { + r#" + Scalar setter `children` should not be generated by default if subform is used. + It can only be generated if req + "# + } + +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/subformer_subform_child.rs" ); +include!( "./only_test/subformer_container_children2.rs" ); +include!( "./only_test/subformer_scalar_children3.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_hashmap.rs b/module/core/former/tests/inc/former_tests/subformer_subform_hashmap.rs new file mode 100644 index 0000000000..2c0ad7e8d7 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_hashmap.rs @@ -0,0 +1,57 @@ +#![ allow( dead_code ) ] + +#[ allow( unused_imports ) ] +use super::*; +#[ allow( unused_imports ) ] +use collection_tools::HashMap; + +// Child struct with Former derived for builder pattern support +#[ derive( Debug, PartialEq, former::Former ) ] +pub struct Child +{ + name : String, + description : String, +} + +// Parent struct to hold commands +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Parent +{ + #[ subform ] + command : HashMap< String, Child >, +} + +impl former::ValToEntry< HashMap< String, Child > > for Child +{ + type Entry = ( String, Child ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.name.clone(), self ) + } +} + +// == begin of generated + +// == end of generated + +#[ test ] +fn basic() +{ + + let got = Parent::former() + .command() + .name( "echo" ) + .description( "prints all subjects and properties" ) // sets additional properties using custom subformer + .end() + .command() + .name( "exit" ) + .description( "just exit" ) // Sets additional properties using using custom subformer + .end() + .form(); + + a_id!( got.command.len(), 2 ); + +} diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_hashmap_custom.rs b/module/core/former/tests/inc/former_tests/subformer_subform_hashmap_custom.rs new file mode 100644 index 0000000000..175197fab8 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_hashmap_custom.rs @@ -0,0 +1,178 @@ +#![ allow( dead_code ) ] + +#[ allow( unused_imports ) ] +use super::*; +#[ allow( unused_imports ) ] +use collection_tools::HashMap; + +// Child struct with Former derived for builder pattern support +#[ derive( Clone, Debug, PartialEq, former::Former ) ] +pub struct Child +{ + name : String, + description : String, +} + +// Parent struct to hold commands +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Parent +{ + #[ subform( setter = false ) ] + command : HashMap< String, Child >, +} + +// Use ChildFormer as custom subformer for ParentFormer to add commands by name. +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + // more generic version + #[ inline( always ) ] + pub fn _children_add_with_closure< Former2, Definition2, Types2 >( self ) -> + Former2 + where + Types2 : former::FormerDefinitionTypes + < + Storage = ChildFormerStorage, + Formed = Self, + Context = Self, + >, + Definition2 : former::FormerDefinition + < + Types = Types2, + End = former::FormingEndClosure< Types2 >, + Storage = ChildFormerStorage, + Formed = Self, + Context = Self, + >, + Definition2::End : former::FormingEnd< Definition2::Types >, + Former2 : former::FormerBegin + < + Definition2, + >, + { + let on_end = | substorage : ChildFormerStorage, super_former : core::option::Option< Self > | -> Self + { + let mut super_former = super_former.unwrap(); + if super_former.storage.command.is_none() + { + super_former.storage.command = Some( Default::default() ); + } + if let Some( ref mut children ) = super_former.storage.command + { + former::ContainerAdd::add + ( + children, + < < HashMap< String, Child > as former::Container >::Val as former::ValToEntry< HashMap< String, Child > > > + ::val_to_entry( former::StoragePreform::preform( substorage ) ) + ); + } + super_former + }; + Former2::former_begin( None, Some( self ), former::FormingEndClosure::new( on_end ) ) + } + + // reuse _command_add + #[ inline( always ) ] + pub fn command( self, name : &str ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._command_add::< ChildFormer< _ >, _, >() + .name( name ) + } + + // that's how you should do custom subformer setters if you can't reuse _command_add + #[ inline( always ) ] + pub fn command2( self, name : &str ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + let on_end = | substorage : ChildFormerStorage, super_former : core::option::Option< Self > | -> Self + { + let mut super_former = super_former.unwrap(); + let preformed = former::StoragePreform::preform( substorage ); + + if super_former.storage.command.is_none() + { + super_former.storage.command = Some( Default::default() ); + } + + // add instance to the container + super_former.storage.command.as_mut().unwrap() + .entry( preformed.name.clone() ) + .or_insert( preformed.clone() ); + + // custom logic to add two instances to the container + super_former.storage.command.as_mut().unwrap() + .entry( format!( "{}_2", preformed.name ) ) + .or_insert( preformed.clone() ); + + super_former + }; + let subformer = ChildAsSubformer::< Self, _ >::begin( None, Some( self ), former::FormingEndClosure::new( on_end ) ); + subformer.name( name ) + } + +} + +impl former::ValToEntry< HashMap< String, Child > > for Child +{ + type Entry = ( String, Child ); + #[ inline( always ) ] + fn val_to_entry( self ) -> Self::Entry + { + ( self.name.clone(), self ) + } +} + +// == begin of generated + +// == end of generated + +#[ test ] +fn custom1() +{ + + let got = Parent::former() + .command( "echo" ) + .description( "prints all subjects and properties" ) // sets additional properties using custom subformer + .end() + .command( "exit" ) + .description( "just exit" ) // Sets additional properties using using custom subformer + .end() + .form(); + + let got = got.command.iter().map( | e | e.0 ).cloned().collect::< collection_tools::HashSet< String > >(); + let exp = collection_tools::hset! + [ + "echo".into(), + "exit".into(), + ]; + a_id!( got, exp ); + +} + +#[ test ] +fn custom2() +{ + + let got = Parent::former() + .command2( "echo" ) + .description( "prints all subjects and properties" ) // sets additional properties using custom subformer + .end() + .command2( "exit" ) + .description( "just exit" ) // Sets additional properties using using custom subformer + .end() + .form(); + + let got = got.command.iter().map( | e | e.0 ).cloned().collect::< collection_tools::HashSet< String > >(); + let exp = collection_tools::hset! + [ + "echo".into(), + "echo_2".into(), + "exit".into(), + "exit_2".into(), + ]; + a_id!( got, exp ); + +} diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_manual.rs b/module/core/former/tests/inc/former_tests/subformer_subform_manual.rs new file mode 100644 index 0000000000..dd75b254c0 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_manual.rs @@ -0,0 +1,204 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + // #[ container( definition = former::VectorDefinition ) ] + // #[ subform ] + #[ scalar( setter = false ) ] + children : Vec< Child >, +} + +// = custom + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, + // Definition::Types : former::FormerDefinitionTypes< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn _children_add_with_closure< Former2, Definition2, Types2 >( self ) -> + Former2 + where + Types2 : former::FormerDefinitionTypes + < + Storage = ChildFormerStorage, + Formed = Self, + Context = Self, + >, + Definition2 : former::FormerDefinition + < + Types = Types2, + End = former::FormingEndClosure< Types2 >, + Storage = ChildFormerStorage, + Formed = Self, + Context = Self, + >, + Definition2::End : former::FormingEnd< Definition2::Types >, + Former2 : former::FormerBegin + < + Definition2, + >, + { + let on_end = | substorage : ChildFormerStorage, super_former : core::option::Option< Self > | -> Self + { + let mut super_former = super_former.unwrap(); + if super_former.storage.children.is_none() + { + super_former.storage.children = Some( Default::default() ); + } + if let Some( ref mut children ) = super_former.storage.children + { + former::ContainerAdd::add + ( + children, + < < Vec< Child > as former::Container >::Val as former::ValToEntry< Vec< Child > > > + ::val_to_entry( former::StoragePreform::preform( substorage ) ) + ); + } + super_former + }; + Former2::former_begin( None, Some( self ), former::FormingEndClosure::new( on_end ) ) + } + + // < < #field_ty as former::Container >::Val as former::ValToEntry< #field_ty > > + + // less generic, but more concise way to define custom subform setter + #[ inline( always ) ] + pub fn child( self, name : &str ) -> + ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< ChildFormer< _ >, _, >() + .name( name ) + } + + // #[ inline( always ) ] + // pub fn _child( self ) -> + // ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + // { + // self._children_add + // ::< < Child as former::EntityToFormer< _ > >::Former, _, >() + // } + + // it is generated + #[ inline( always ) ] + pub fn _child( self ) -> + < < Vec< Child > as former::Container >::Entry as former::EntityToFormer + < + // ChildFormerDefinition< Self, Self, ParentFormerAddChildrenEnd< Definition > >, + < + < Vec< Child > as former::Container >::Entry as former::EntityToDefinition< Self, Self, ParentFormerAddChildrenEnd< Definition > > + >::Definition, + > + >::Former + { + self._children_add + ::< < < Vec< Child > as former::Container >::Entry as former::EntityToFormer< _ > >::Former, _, >() + } + +} + +// == begin of generated for Parent in context of attribute subform + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, + // Definition::Types : former::FormerDefinitionTypes< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn _children_add< Former2, Definition2 >( self ) -> + Former2 + where + Definition2 : former::FormerDefinition + < + End = ParentFormerAddChildrenEnd< Definition >, + Storage = < Child as former::EntityToStorage >::Storage, + Formed = Self, + Context = Self, + >, + Definition2::Types : former::FormerDefinitionTypes + < + Storage = < Child as former::EntityToStorage >::Storage, + Formed = Self, + Context = Self, + >, + Former2 : former::FormerBegin< Definition2 >, + { + Former2::former_begin( None, Some( self ), ParentFormerAddChildrenEnd::default() ) + } + +} + +/// Handles the completion of and element of subformer's container. +pub struct ParentFormerAddChildrenEnd< Definition > +{ + _phantom : core::marker::PhantomData< fn( Definition ) >, +} + +impl< Definition > Default +for ParentFormerAddChildrenEnd< Definition > +{ + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } +} + +impl< Types2, Definition > former::FormingEnd< Types2, > +for ParentFormerAddChildrenEnd< Definition > +where + Definition : former::FormerDefinition + < + Storage = < Parent as former::EntityToStorage >::Storage, + >, + Types2 : former::FormerDefinitionTypes + < + Storage = < < Vec< Child > as former::Container >::Entry as former::EntityToStorage >::Storage, + Formed = ParentFormer< Definition >, + Context = ParentFormer< Definition >, + >, +{ + #[ inline( always ) ] + fn call + ( + &self, + substorage : Types2::Storage, + super_former : core::option::Option< Types2::Context >, + ) + -> Types2::Formed + { + let mut super_former = super_former.unwrap(); + if super_former.storage.children.is_none() + { + super_former.storage.children = Some( Default::default() ); + } + if let Some( ref mut fields ) = super_former.storage.children + { + former::ContainerAdd::add( fields, former::StoragePreform::preform( substorage ) ); + } + super_former + } +} + +// == end of generated for Parent in context of attribute subform + +include!( "./only_test/subformer_subform_child.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_named.rs b/module/core/former/tests/inc/former_tests/subformer_subform_named.rs new file mode 100644 index 0000000000..e834e8b30d --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_named.rs @@ -0,0 +1,62 @@ +#![ deny( missing_docs ) ] +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ subform( name = _child ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, + // Definition::Types : former::FormerDefinitionTypes< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn children( self ) -> &'static str + { + r#" + Scalar setter `children` should not be generated by default if subform is used. + It can only be generated if req + "# + } + + #[ inline( always ) ] + pub fn child( self, name : &str ) -> + ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< ChildFormer< _ >, _, >() + .name( name ) + } + + // #[ inline( always ) ] + // pub fn _child( self ) -> + // ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + // { + // self._children_add + // ::< < Child as former::EntityToFormer< _ > >::Former, _, >() + // } + +} + +// == begin of generated + +// == end of generated + +include!( "./only_test/subformer_subform_child.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_named_manual.rs b/module/core/former/tests/inc/former_tests/subformer_subform_named_manual.rs new file mode 100644 index 0000000000..c169a74d79 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_named_manual.rs @@ -0,0 +1,72 @@ +#![ deny( missing_docs ) ] +#![ allow( dead_code ) ] + +use super::*; + +/// Parameter description. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent required for the template. +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ derive( Debug, Default, PartialEq, the_module::Former ) ] #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ subform ] + // #[ scalar( setter = false ) ] + children : Vec< Child >, +} + +// == begin of custom + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn child( self, name : &str ) -> + ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< ChildFormer< _ >, _, >() + .name( name ) + } + + // #[ inline( always ) ] + // pub fn _child( self ) -> + // ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + // { + // self._children_add + // ::< < Child as former::EntityToFormer< _ > >::Former, _, >() + // } + + #[ inline( always ) ] + pub fn _child( self ) -> + < < Vec< Child > as former::Container >::Entry as former::EntityToFormer + < + // ChildFormerDefinition< Self, Self, ParentFormerAddChildrenEnd< Definition > >, + < + < Vec< Child > as former::Container >::Entry as former::EntityToDefinition< Self, Self, ParentFormerAddChildrenEnd< Definition > > + >::Definition, + > + >::Former + { + self._children_add + ::< < < Vec< Child > as former::Container >::Entry as former::EntityToFormer< _ > >::Former, _, >() + } + +} + +// == end of custom + +// == begin of generated for Parent in context of attribute subform + +// == end of generated for Parent in context of attribute subform + +include!( "./only_test/subformer_subform_child.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_setter_off.rs b/module/core/former/tests/inc/former_tests/subformer_subform_setter_off.rs new file mode 100644 index 0000000000..e5a2f9eb61 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_setter_off.rs @@ -0,0 +1,49 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Child +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent + +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + #[ subform( setter = false ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn children( self ) -> &'static str + { + r#" + Scalar setter `children` should not be generated by default if subform is used. + It can only be generated if req + "# + } + + #[ inline( always ) ] + pub fn children2( self, name : &str ) -> + ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< ChildFormer< _ >, _, >() + .name( name ) + } + +} + +include!( "./only_test/subformer_subform_children2.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_subform_setter_on.rs b/module/core/former/tests/inc/former_tests/subformer_subform_setter_on.rs new file mode 100644 index 0000000000..29378ff208 --- /dev/null +++ b/module/core/former/tests/inc/former_tests/subformer_subform_setter_on.rs @@ -0,0 +1,44 @@ +#![ allow( dead_code ) ] + +use super::*; + +/// Child +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +pub struct Child +{ + name : String, + data : bool, +} + +/// Parent + +#[ derive( Debug, Default, PartialEq, the_module::Former ) ] +// #[ debug ] +// #[ derive( Debug, Default, PartialEq ) ] +pub struct Parent +{ + // Such parameters switch off generation of front-end subform setter and switch on scalar setter. + // Without explicit scalar_setter( true ) scalar setter is not generated. + #[ subform( setter = false ) ] + #[ scalar( setter = true ) ] + children : Vec< Child >, +} + +impl< Definition > ParentFormer< Definition > +where + Definition : former::FormerDefinition< Storage = < Parent as former::EntityToStorage >::Storage >, +{ + + #[ inline( always ) ] + pub fn children2( self, name : &str ) -> + ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + { + self._children_add + ::< ChildFormer< _ >, _, >() + .name( name ) + } + +} + +include!( "./only_test/subformer_scalar_children.rs" ); +include!( "./only_test/subformer_subform_children2.rs" ); diff --git a/module/core/former/tests/inc/former_tests/subformer_vec.rs b/module/core/former/tests/inc/former_tests/subformer_vec.rs deleted file mode 100644 index 7a7244fece..0000000000 --- a/module/core/former/tests/inc/former_tests/subformer_vec.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![ allow( dead_code ) ] - -use super::*; -#[ allow( unused_imports ) ] -use collection_tools::Vec; - -#[ test ] -fn push() -{ - - // - - let got : Vec< String > = the_module::VectorSubformer::new() - .push( "a" ) - .push( "b" ) - .form(); - let exp = vec! - [ - "a".to_string(), - "b".to_string(), - ]; - a_id!( got, exp ); - -} - -#[ test ] -fn replace() -{ - - let got : Vec< String > = the_module::VectorSubformer::new() - .push( "x" ) - .replace( vec![ "a".to_string(), "b".to_string() ] ) - .form(); - let exp = vec! - [ - "a".to_string(), - "b".to_string(), - ]; - a_id!( got, exp ); - -} diff --git a/module/core/former/tests/inc/former_tests/unsigned_primitive_types.rs b/module/core/former/tests/inc/former_tests/unsigned_primitive_types.rs index 32bb942485..abfbe7d183 100644 --- a/module/core/former/tests/inc/former_tests/unsigned_primitive_types.rs +++ b/module/core/former/tests/inc/former_tests/unsigned_primitive_types.rs @@ -47,91 +47,90 @@ tests_impls! // -// zzz : make it working fn with_u16() { -// #[ derive( Debug, PartialEq, the_module::Former ) ] -// pub struct Counter -// { -// count : u16, -// } -// -// let counter = Counter::former() -// .count( 0 ) -// .form(); -// -// let expected = Counter -// { -// count : 0, -// }; -// -// a_id!( counter, expected ); + #[ derive( Debug, PartialEq, the_module::Former ) ] + pub struct Counter + { + count : u16, + } + + let counter = Counter::former() + .count( 0u16 ) + .form(); + + let expected = Counter + { + count : 0, + }; + + a_id!( counter, expected ); } // fn with_u32() { - // #[ derive( Debug, PartialEq, Former ) ] - // pub struct Counter - // { - // count : u32, - // } - // - // let counter = Counter::former() - // .count( 0 ) - // .form(); - // - // let expected = Counter - // { - // count : 0, - // }; - // - // a_id!( counter, expected ); + #[ derive( Debug, PartialEq, the_module::Former ) ] + pub struct Counter + { + count : u32, + } + + let counter = Counter::former() + .count( 0u32 ) + .form(); + + let expected = Counter + { + count : 0, + }; + + a_id!( counter, expected ); } // fn with_u64() { - // #[ derive( Debug, PartialEq, Former ) ] - // pub struct Counter - // { - // count : u64, - // } - // - // let counter = Counter::former() - // .count( 0 ) - // .form(); - // - // let expected = Counter - // { - // count : 0, - // }; - // - // a_id!( counter, expected ); + #[ derive( Debug, PartialEq, the_module::Former ) ] + pub struct Counter + { + count : u64, + } + + let counter = Counter::former() + .count( 0u64 ) + .form(); + + let expected = Counter + { + count : 0, + }; + + a_id!( counter, expected ); } // fn with_usize() { - // #[ derive( Debug, PartialEq, Former ) ] - // pub struct Counter - // { - // count : usize, - // } - // - // let counter = Counter::former() - // .count( 0 ) - // .form(); - // - // let expected = Counter - // { - // count : 0, - // }; - // - // a_id!( counter, expected ); + #[ derive( Debug, PartialEq, the_module::Former ) ] + pub struct Counter + { + count : usize, + } + + let counter = Counter::former() + .count( 0usize ) + .form(); + + let expected = Counter + { + count : 0, + }; + + a_id!( counter, expected ); } } diff --git a/module/core/former/tests/inc/former_tests/visibility.rs b/module/core/former/tests/inc/former_tests/visibility.rs new file mode 100644 index 0000000000..7df53933ac --- /dev/null +++ b/module/core/former/tests/inc/former_tests/visibility.rs @@ -0,0 +1,25 @@ +//! Structure must be public. +//! Otherwise public trait can't have it as type. + +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( Debug, PartialEq, former::Former ) ] +// #[ debug ] +// #[ derive( Debug, PartialEq ) ] +pub struct Foo +{ + bar : i32, +} + +// == begin of generated + +// == end of generated + +#[ test ] +fn basic() +{ + let got = Foo::former().bar( 13 ).form(); + let exp = Foo { bar : 13 }; + a_id!( got, exp ); +} \ No newline at end of file diff --git a/module/core/former/tests/inc/mod.rs b/module/core/former/tests/inc/mod.rs index 8eff590826..069cbec9e2 100644 --- a/module/core/former/tests/inc/mod.rs +++ b/module/core/former/tests/inc/mod.rs @@ -1,3 +1,5 @@ +// #![ deny( missing_docs ) ] + #[ allow( unused_imports ) ] use super::*; @@ -7,19 +9,35 @@ mod former_tests #[ allow( unused_imports ) ] use super::*; + #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] + mod container_former_common; + #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] + mod container_former_vec; + #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] + mod container_former_hashset; + #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] + mod container_former_hashmap; + + mod a_basic_manual; + mod a_basic; mod a_primitives_manual; - mod a_containers_without_runtime_manual; - mod a_containers_without_runtime; + mod a_primitives; + + mod a_containers_scalar; #[ cfg( not( feature = "no_std" ) ) ] - mod a_containers_with_runtime_manual; + mod a_containers_manual; #[ cfg( not( feature = "no_std" ) ) ] - mod a_containers_with_runtime ; + mod a_containers; mod attribute_default_container; mod attribute_default_primitive; + mod attribute_default_conflict; + mod attribute_storage_with_end; + mod attribute_storage_with_mutator; mod attribute_perform; mod attribute_setter; mod attribute_alias; + mod attribute_feature; mod string_slice_manual; mod string_slice; @@ -27,6 +45,7 @@ mod former_tests mod default_user_type; mod user_type_no_default; mod user_type_no_debug; + mod visibility; mod name_collision_former_hashmap_without_parameter; mod name_collision_former_vector_without_parameter; @@ -41,21 +60,49 @@ mod former_tests mod parametrized_struct_imm; #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] mod parametrized_struct_where; + mod parametrized_field; + mod parametrized_field_where; - #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - mod subformer_basic_manual; #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] mod subformer_basic; - #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - mod subformer_vec; - #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - mod subformer_hashset; - #[ cfg( any( not( feature = "no_std" ), feature = "use_alloc" ) ) ] - mod subformer_hashmap; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_container; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_container_manual; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_container_implicit; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_container_setter_off; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_container_named; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_container_custom; + + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_manual; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_named; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_named_manual; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_setter_off; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_setter_on; + + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_hashmap; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_hashmap_custom; #[ cfg( any( not( feature = "no_std" ) ) ) ] - mod subformer_shortcut; + mod subformer_subform_and_container; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_and_container_private; + #[ cfg( any( not( feature = "no_std" ) ) ) ] + mod subformer_subform_and_container_parametrized; } @@ -96,6 +143,8 @@ only_for_terminal_module! // stable have different information about error // that's why these tests are active only for nightly + + #[ cfg( feature = "derive_former" ) ] #[ test_tools::nightly ] #[ test ] fn former_trybuild() @@ -104,11 +153,25 @@ only_for_terminal_module! println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); let t = test_tools::compiletime::TestCases::new(); - t.compile_fail( "tests/inc/compiletime/former_bad_attr.rs" ); - t.pass( "tests/inc/compiletime/former_hashmap_without_parameter.rs" ); - t.pass( "tests/inc/compiletime/former_vector_without_parameter.rs" ); + t.compile_fail( "tests/inc/former_tests/compiletime/field_attr_bad.rs" ); + t.compile_fail( "tests/inc/former_tests/compiletime/struct_attr_bad.rs" ); + t.pass( "tests/inc/former_tests/compiletime/hashmap_without_parameter.rs" ); + t.pass( "tests/inc/former_tests/compiletime/vector_without_parameter.rs" ); + + } + + // stable have different information about error + // that's why these tests are active only for nightly + #[ test_tools::nightly ] + #[ test ] + fn components_trybuild() + { + + println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); + let _t = test_tools::compiletime::TestCases::new(); - //t.compile_fail( "tests/inc/compiletime/components_component_from_debug.rs" ); + // zzz : make it working test + //t.run( "tests/inc/components_tests/compiletime/components_component_from_debug.rs" ); } diff --git a/module/core/former/tests/inc/only_test/containers_with_runtime.rs b/module/core/former/tests/inc/only_test/containers_with_runtime.rs deleted file mode 100644 index ceeb84abaa..0000000000 --- a/module/core/former/tests/inc/only_test/containers_with_runtime.rs +++ /dev/null @@ -1,264 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; - -// - -tests_impls_optional! -{ - - // - - fn internals() - { - - // test.case( "vector : construction" ); - - let former = Struct1::former(); - a_id!( former.storage.vec_1, None ); - a_id!( former.storage.hashmap_strings_1, None ); - a_id!( former.storage.hashset_strings_1, None ); - a_id!( former.context, None ); - a_id!( print!( "{:?}", former.on_end ), print!( "{:?}", Some( the_module::ReturnFormed ) ) ); - let former2 = Struct1Former::< Struct1, the_module::ReturnFormed >::new(); - a_id!( std::mem::size_of_val( &former ), std::mem::size_of_val( &former2 ) ); - - let command = Struct1::former().form(); - a_id!( command.vec_1, Vec::< String >::new() ); - a_id!( command.hashmap_strings_1, hmap!{} ); - a_id!( command.hashset_strings_1, hset![] ); - - let command = Struct1::former().perform(); - a_id!( command.vec_1, Vec::< String >::new() ); - a_id!( command.hashmap_strings_1, hmap!{} ); - a_id!( command.hashset_strings_1, hset![] ); - - let command = Struct1::former().end(); - a_id!( command.vec_1, Vec::< String >::new() ); - a_id!( command.hashmap_strings_1, hmap!{} ); - a_id!( command.hashset_strings_1, hset![] ); - - } - - // - - fn test_vector() - { - - // test.case( "vector : implicit construction" ); - - let command = Struct1::former() - .vec_1().push( "ghi" ).push( "klm" ).end() - .form() - ; - // dbg!( &command ); - - let expected = Struct1 - { - vec_1 : vec![ "ghi".to_string(), "klm".to_string() ], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - - // test.case( "vector : replace" ); - - let command = Struct1::former() - .vec_1().replace( vec![ "a".to_string(), "bc".to_string(), "def".to_string() ] ).end() - .form(); - let expected = Struct1 - { - vec_1 : vec![ "a".to_string(), "bc".to_string(), "def".to_string() ], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - - let command = Struct1::former() - .vec_1().push( "x" ).replace( vec![ "a".to_string(), "bc".to_string(), "def".to_string() ] ).end() - .form(); - let expected = Struct1 - { - vec_1 : vec![ "a".to_string(), "bc".to_string(), "def".to_string() ], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - - // test.case( "vector : replace and push" ); - - let command = Struct1::former() - .vec_1().replace( vec![ "a".to_string(), "bc".to_string(), "def".to_string() ] ).push( "gh" ).end() - .form(); - // dbg!( &command ); - - let expected = Struct1 - { - vec_1 : vec![ "a".to_string(), "bc".to_string(), "def".to_string(), "gh".to_string() ], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - } - - // - - fn test_hashmap() - { - - // test.case( "implicit construction" ); - - let command = Struct1::former() - .hashmap_strings_1().insert( "k1", "v1" ).insert( "k2", "v2" ).end() - .form() - ; - // dbg!( &command ); - - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - - // test.case( "replace" ); - - let command = Struct1::former() - .hashmap_strings_1().replace( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ).end() - .form() - ; - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - - let command = Struct1::former() - .hashmap_strings_1().insert( "x", "v1" ).replace( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ).end() - .form() - ; - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - - // test.case( "replace and insert" ); - - let command = Struct1::former() - .hashmap_strings_1().replace( hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() } ).insert( "k3", "v3" ).end() - .form() - ; - // dbg!( &command ); - - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string(), "k3".to_string() => "v3".to_string() }, - hashset_strings_1 : hset!{}, - }; - a_id!( command, expected ); - } - - // - - fn test_hashset() - { - - // test.case( "implicit construction" ); - - let command = Struct1::former() - .hashset_strings_1().insert( "v1" ).insert( "v2" ).end() - .form() - ; - // dbg!( &command ); - - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{ "v1".to_string(), "v2".to_string() }, - }; - a_id!( command, expected ); - - // test.case( "replace" ); - - let command = Struct1::former() - .hashset_strings_1().replace( hset!{ "v1".to_string(), "v2".to_string() } ).end() - .form() - ; - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{ "v1".to_string(), "v2".to_string() }, - }; - a_id!( command, expected ); - - let command = Struct1::former() - .hashset_strings_1().insert( "x" ).replace( hset!{ "v1".to_string(), "v2".to_string() } ).end() - .form() - ; - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{ "v1".to_string(), "v2".to_string() }, - }; - a_id!( command, expected ); - - // test.case( "replace and insert" ); - - let command = Struct1::former() - .hashset_strings_1().replace( hset!{ "v1".to_string(), "v2".to_string() } ).insert( "v3" ).end() - .form() - ; - // dbg!( &command ); - - let expected = Struct1 - { - vec_1 : vec![], - hashmap_strings_1 : hmap!{}, - hashset_strings_1 : hset!{ "v1".to_string(), "v2".to_string(), "v3".to_string() }, - }; - a_id!( command, expected ); - } - - // - - fn test_complex() - { - - let command = Struct1::former() - .vec_1().push( "ghi" ).push( "klm" ).end() - .hashmap_strings_1().insert( "k1", "v1" ).insert( "k2", "v2" ).end() - .hashset_strings_1().insert( "k1" ).end() - .form(); - // dbg!( &command ); - - let expected = Struct1 - { - vec_1 : vec![ "ghi".to_string(), "klm".to_string() ], - hashmap_strings_1 : hmap!{ "k1".to_string() => "v1".to_string(), "k2".to_string() => "v2".to_string() }, - hashset_strings_1 : hset!{ "k1".to_string() }, - }; - a_id!( command, expected ); - - } - -} - -// - -tests_index! -{ - internals, - test_vector, - test_hashmap, - test_hashset, - test_complex, -} diff --git a/module/core/former/tests/inc/only_test/string_slice.rs b/module/core/former/tests/inc/only_test/string_slice.rs deleted file mode 100644 index cd07841dd3..0000000000 --- a/module/core/former/tests/inc/only_test/string_slice.rs +++ /dev/null @@ -1,51 +0,0 @@ -#[ allow( unused_imports ) ] -use super::*; -#[ allow( unused_imports ) ] -use test_tools::exposed::*; - -// - -tests_impls! -{ - fn test_complex() - { - // test.case( "default" ); - - let command = Struct1::former().form(); - let expected = Struct1 - { - string_slice_1 : "", - }; - a_id!( command, expected ); - - // test.case( "from slice" ); - - let command = Struct1::former() - .string_slice_1( "abc" ) - .form(); - let expected = Struct1 - { - string_slice_1 : "abc", - }; - a_id!( command, expected ); - -// // test.case( "from string" ); -// -// let command = Struct1::former() -// .string_slice_1( "abc".to_string() ) -// .form(); -// let expected = Struct1 -// { -// string_slice_1 : "abc", -// }; -// a_id!( command, expected ); - - } -} - -// - -tests_index! -{ - test_complex, -} diff --git a/module/core/former/tests/inc/only_test/with_field_under_feature.rs b/module/core/former/tests/inc/only_test/with_field_under_feature.rs deleted file mode 100644 index 2623f7579b..0000000000 --- a/module/core/former/tests/inc/only_test/with_field_under_feature.rs +++ /dev/null @@ -1,9 +0,0 @@ -// xxx : need to fix -// #[ derive( Former ) ] -// struct Foo -// { -// #[ cfg( feature = "baz" ) ] -// bar : i32, -// } - -// error => Unknown attribute #[cfg(feature = "baz")] \ No newline at end of file diff --git a/module/core/former_meta/Cargo.toml b/module/core/former_meta/Cargo.toml index d5b80364a1..4bb60c3283 100644 --- a/module/core/former_meta/Cargo.toml +++ b/module/core/former_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "former_meta" -version = "0.14.0" +version = "1.0.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -46,7 +46,7 @@ full = [ ] enabled = [ "macro_tools/enabled", "iter_tools/enabled" ] -derive_former = [] +derive_former = [ "convert_case" ] derive_components = [] derive_component_assign = [] derive_components_assign = [ "derive_components", "derive_component_assign", "convert_case" ] diff --git a/module/core/former_meta/Readme.md b/module/core/former_meta/Readme.md index 0cdd97f471..e7eeaec814 100644 --- a/module/core/former_meta/Readme.md +++ b/module/core/former_meta/Readme.md @@ -2,8 +2,7 @@ # Module :: former_meta - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml)[![docs.rs](https://img.shields.io/docsrs/former_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/former_meta)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fformer_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20former_meta_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_former_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/former_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/former_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Former - a variation of builder pattern. Implementation of its derive macro. Should not be used independently, instead use module::former which relies on the module. diff --git a/module/core/former_meta/src/derive/component_assign.rs b/module/core/former_meta/src/component/component_assign.rs similarity index 89% rename from module/core/former_meta/src/derive/component_assign.rs rename to module/core/former_meta/src/component/component_assign.rs index 1120c9da64..4677f27b0f 100644 --- a/module/core/former_meta/src/derive/component_assign.rs +++ b/module/core/former_meta/src/component/component_assign.rs @@ -23,9 +23,15 @@ pub fn component_assign( input : proc_macro::TokenStream ) -> Result< proc_macro if has_debug { - diag::debug_report_print( "derive : ComponentAssign", original_input, &result ); + let about = format!( "derive : ComponentAssign\nstructure : {0}", &parsed.item_name ); + diag::report_print( about, &original_input, &result ); } + // if has_debug + // { + // diag::report_print( "derive : ComponentAssign", original_input, &result ); + // } + Ok( result ) } diff --git a/module/core/former_meta/src/derive/component_from.rs b/module/core/former_meta/src/component/component_from.rs similarity index 87% rename from module/core/former_meta/src/derive/component_from.rs rename to module/core/former_meta/src/component/component_from.rs index dda6740aa5..994206b996 100644 --- a/module/core/former_meta/src/derive/component_from.rs +++ b/module/core/former_meta/src/component/component_from.rs @@ -22,9 +22,15 @@ pub fn component_from( input : proc_macro::TokenStream ) -> Result< proc_macro2: if has_debug { - diag::debug_report_print( "derive : ComponentFrom", original_input, &result ); + let about = format!( "derive : ComponentFrom\nstructure : {0}", &parsed.item_name ); + diag::report_print( about, &original_input, &result ); } + // if has_debug + // { + // diag::report_print( "derive : ComponentFrom", original_input, &result ); + // } + Ok( result ) } diff --git a/module/core/former_meta/src/derive/components_assign.rs b/module/core/former_meta/src/component/components_assign.rs similarity index 92% rename from module/core/former_meta/src/derive/components_assign.rs rename to module/core/former_meta/src/component/components_assign.rs index ac4ecac166..bcf5cc0bc9 100644 --- a/module/core/former_meta/src/derive/components_assign.rs +++ b/module/core/former_meta/src/component/components_assign.rs @@ -16,7 +16,7 @@ pub fn components_assign( input : proc_macro::TokenStream ) -> Result< proc_macr let has_debug = attr::has_debug( parsed.item.attrs.iter() )?; // name - let item_name = parsed.item_name; + let item_name = &parsed.item_name; let trait_name = format!( "{}ComponentsAssign", item_name ); let trait_ident = syn::Ident::new( &trait_name, item_name.span() ); let method_name = format!( "{}_assign", item_name.to_string().to_case( Case::Snake ) ); @@ -68,8 +68,15 @@ pub fn components_assign( input : proc_macro::TokenStream ) -> Result< proc_macr if has_debug { - diag::debug_report_print( "derive : ComponentsAssign", original_input, &result ); + let about = format!( "derive : ComponentsAssign\nstructure : {0}", item_name ); + diag::report_print( about, &original_input, &result ); } + + // if has_debug + // { + // diag::report_print( "derive : ComponentsAssign", original_input, &result ); + // } + Ok( result ) } diff --git a/module/core/former_meta/src/derive/from_components.rs b/module/core/former_meta/src/component/from_components.rs similarity index 93% rename from module/core/former_meta/src/derive/from_components.rs rename to module/core/former_meta/src/component/from_components.rs index b7d1d9f58f..62ae0615a9 100644 --- a/module/core/former_meta/src/derive/from_components.rs +++ b/module/core/former_meta/src/component/from_components.rs @@ -69,8 +69,15 @@ pub fn from_components( input : proc_macro::TokenStream ) -> Result< proc_macro2 if has_debug { - diag::debug_report_print( "derive : FromComponents", original_input, &result ); + let about = format!( "derive : FromComponents\nstructure : {0}", &parsed.item_name ); + diag::report_print( about, &original_input, &result ); } + + // if has_debug + // { + // diag::report_print( "derive : FromComponents", original_input, &result ); + // } + Ok( result.into() ) } diff --git a/module/core/former_meta/src/derive/former.rs b/module/core/former_meta/src/derive/former.rs deleted file mode 100644 index 5ca9feb66c..0000000000 --- a/module/core/former_meta/src/derive/former.rs +++ /dev/null @@ -1,1095 +0,0 @@ - -use super::*; -use iter_tools::{ Itertools, process_results }; -use macro_tools::{ attr, diag, generics, container_kind, typ, Result }; -use proc_macro2::TokenStream; - -/// -/// Descriptor of a field. -/// - -#[ allow( dead_code ) ] -struct FormerField< 'a > -{ - pub attrs : Attributes, - pub vis : &'a syn::Visibility, - pub ident : &'a syn::Ident, - pub colon_token : &'a Option< syn::token::Colon >, - pub ty : &'a syn::Type, - pub non_optional_ty : &'a syn::Type, - pub is_optional : bool, - pub of_type : container_kind::ContainerKind, -} - -/// -/// Attributes of the field. -/// - -struct Attributes -{ - default : Option< AttributeDefault >, - setter : Option< AttributeSetter >, - // #[ allow( dead_code ) ] - subformer : Option< AttributeFormer >, - alias : Option< AttributeAlias >, -} - -impl Attributes -{ - fn parse( attributes : & Vec< syn::Attribute > ) -> Result< Self > - { - let mut default = None; - let mut setter = None; - let mut subformer = None; - let mut alias = None; - for attr in attributes - { - let key_ident = attr.path().get_ident() - .ok_or_else( || syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ) )?; - let key_str = format!( "{}", key_ident ); - match key_str.as_ref() - { - "default" => - { - match attr.meta - { - syn::Meta::List( ref meta_list ) => - { - default.replace( syn::parse2::< AttributeDefault >( meta_list.tokens.clone() )? ); - }, - _ => return_syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ), - } - } - "setter" => - { - match attr.meta - { - syn::Meta::List( ref meta_list ) => - { - setter.replace( syn::parse2::< AttributeSetter >( meta_list.tokens.clone() )? ); - }, - _ => return_syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ), - } - // let attr_setter = syn::parse2::< AttributeSetter >( attr.tokens.clone() )?; - // setter.replace( attr_setter ); - } - "subformer" => - { - match attr.meta - { - syn::Meta::List( ref meta_list ) => - { - subformer.replace( syn::parse2::< AttributeFormer >( meta_list.tokens.clone() )? ); - }, - _ => return_syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ), - } - // let attr_former = syn::parse2::< AttributeFormer >( attr.tokens.clone() )?; - // subformer.replace( attr_former ); - } - "alias" => - { - match attr.meta - { - syn::Meta::List( ref meta_list ) => - { - alias.replace( syn::parse2::< AttributeAlias >( meta_list.tokens.clone() )? ); - }, - _ => return_syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ), - } - // let attr_alias = syn::parse2::< AttributeAlias >( attr.tokens.clone() )?; - // alias.replace( attr_alias ); - } - "doc" => - { - } - _ => - { - return Err( syn_err!( attr, "Unknown attribute {}", qt!{ #attr } ) ); - } - } - } - - Ok( Attributes { default, setter, subformer, alias } ) - } -} - -/// -/// Attribute to hold information about method to call after form. -/// -/// `#[ perform( fn after1< 'a >() -> Option< &'a str > ) ]` -/// - -#[ allow( dead_code ) ] -struct AttributeFormAfter -{ - // paren_token : syn::token::Paren, - signature : syn::Signature, -} - -impl syn::parse::Parse for AttributeFormAfter -{ - fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > - { - // let input2; - Ok( Self - { - // paren_token : syn::parenthesized!( input2 in input ), - // signature : input2.parse()?, - signature : input.parse()?, - }) - } -} - -/// -/// Attribute to hold information about default value. -/// -/// `#[ default( 13 ) ]` -/// - -#[ allow( dead_code ) ] -struct AttributeDefault -{ - // eq_token : syn::Token!{ = }, - // paren_token : syn::token::Paren, - expr : syn::Expr, -} - -impl syn::parse::Parse for AttributeDefault -{ - fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > - { - // let input2; - Ok( Self - { - // paren_token : syn::parenthesized!( input2 in input ), - // eq_token : input.parse()?, - // expr : input2.parse()?, - expr : input.parse()?, - }) - } -} - -/// -/// Attribute to enable/disable setter generation. -/// -/// `#[ setter( false ) ]` -/// - -#[ allow( dead_code ) ] -struct AttributeSetter -{ - // paren_token : syn::token::Paren, - condition : syn::LitBool, -} - -impl syn::parse::Parse for AttributeSetter -{ - fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > - { - // let input2; - Ok( Self - { - // paren_token : syn::parenthesized!( input2 in input ), - // condition : input2.parse()?, - condition : input.parse()?, - }) - } -} - -/// -/// Attribute to enable/disable former generation. -/// Also known as subformers, used for aggregation relationship, when a struct holds another struct, which needs to be build by invoking multiple methods -/// Typical example is a struct holding a `Vec` -/// -/// `#[ subformer( former::VectorSubformer ) ]` -/// - -#[ allow( dead_code ) ] -struct AttributeFormer -{ - // paren_token : syn::token::Paren, - expr : syn::Type, -} - -impl syn::parse::Parse for AttributeFormer -{ - fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > - { - // let input2; - Ok( Self - { - // paren_token : syn::parenthesized!( input2 in input ), - // expr : input2.parse()?, - expr : input.parse()?, - }) - } -} - -/// -/// Attribute to create alias. -/// -/// `#[ alias( name ) ]` -/// - -#[ allow( dead_code ) ] -struct AttributeAlias -{ - // paren_token : syn::token::Paren, - alias : syn::Ident, -} - -impl syn::parse::Parse for AttributeAlias -{ - fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > - { - // let input2; - Ok( Self - { - // paren_token : syn::parenthesized!( input2 in input ), - // alias : input2.parse()?, - alias : input.parse()?, - }) - } -} - -/// -/// Is type under Option. -/// - -fn is_optional( ty : &syn::Type ) -> bool -{ - typ::type_rightmost( ty ) == Some( "Option".to_string() ) -} - -/// -/// Extract the first parameter of the type if such exist. -/// - -fn parameter_internal_first( ty : &syn::Type ) -> Result< &syn::Type > -{ - typ::type_parameters( ty, 0 ..= 0 ) - .first() - .copied() - .ok_or_else( || syn_err!( ty, "Expects at least one parameter here:\n {}", qt!{ #ty } ) ) -} - -/// -/// Generate fields for initializer of a struct setting each field to `None`. -/// -/// Used for initializing a Container, where on initialization all fields are None. User can alter them through builder pattern -/// -/// ### Basic use-case. of output -/// -/// ```ignore -/// int_1 : core::option::Option::None, -/// string_1 : core::option::Option::None, -/// int_optional_1 : core::option::Option::None, -/// ``` -/// - -#[ inline( always ) ] -fn field_none_map( field : &FormerField< '_ > ) -> TokenStream -{ - let ident = Some( field.ident.clone() ); - let tokens = qt! { ::core::option::Option::None }; - let ty2 : syn::Type = syn::parse2( tokens ).unwrap(); - - qt! - { - #ident : #ty2 - } -} - -/// -/// Generate field of the former for a field of the structure -/// -/// Used to generate a Container -/// -/// ### Basic use-case. of output -/// -/// ```ignore -/// pub int_1 : core::option::Option< i32 >, -/// pub string_1 : core::option::Option< String >, -/// pub int_optional_1 : core::option::Option< i32 >, -/// pub string_optional_1 : core::option::Option< String >, -/// ``` -/// - -#[ inline( always ) ] -fn field_optional_map( field : &FormerField< '_ > ) -> TokenStream -{ - let ident = Some( field.ident.clone() ); - let ty = field.ty.clone(); - - // let ty2 = if is_optional( &ty ) - let ty2 = if field.is_optional - { - qt! { #ty } - } - else - { - qt! { ::core::option::Option< #ty > } - }; - - qt! - { - pub #ident : #ty2 - } - -} - -/// -/// Generate code converting a field of the former to the field of the structure. -/// -/// In simple terms, used on `form()` call to unwrap contained values from the former's storage. -/// Will try to use default values if no values supplied by the former and the type implements `Default` trait. -/// -/// ### Generated code will look similar to this : -/// -/// ```ignore -/// let int_1 : i32 = if self.storage.int_1.is_some() -/// { -/// // if int_1 is optional -/// Some( self.storage.int_1.take().unwrap() ) -/// -/// // if int_1 isn't optional -/// self.storage.int_1.take().unwrap() -/// } -/// else -/// { -/// // if int_1 is optional and has default -/// Some( i32::default().into() ) -/// -/// // if int_1 is optional and doesn't have default -/// None -/// -/// // if int_1 isn't optional and has default -/// i32::default().into() -/// -/// // if int_1 isn't optional and hasn't default -/// panic!( "Field 'int_1' isn't initialized" ) -/// }; -/// ``` -/// - -#[ inline( always ) ] -fn field_form_map( field : &FormerField< '_ > ) -> Result< TokenStream > -{ - let ident = field.ident; - let ty = field.ty; - let default = field.attrs.default.as_ref() - .map( | attr_default | &attr_default.expr ); - - let tokens = if field.is_optional - { - - let _else = match default - { - None => - { - qt! - { - ::core::option::Option::None - } - } - - Some( default_val ) => - { - qt! - { - ::core::option::Option::Some( ( #default_val ).into() ) - } - } - }; - - qt! - { - let #ident = if self.storage.#ident.is_some() - { - ::core::option::Option::Some( self.storage.#ident.take().unwrap() ) - } - else - { - #_else - }; - } - - } - else - { - - let _else = match default - { - None => - { - let panic_msg = format!( "Field '{}' isn't initialized", ident ); - qt! - { - { - // By hardly utilizing deref coercion, we achieve conditional trait implementation - trait MaybeDefault< T > - { - fn maybe_default( self : &Self ) -> T { panic!( #panic_msg ) } - } - - // Panic on non-`Default` types - impl< T > MaybeDefault< T > - for &::core::marker::PhantomData< T > - {} - - // Return default value on `Default`` types - impl< T > MaybeDefault< T > - for ::core::marker::PhantomData< T > - where T : ::core::default::Default, - { - fn maybe_default( self : &Self ) -> T - { - T::default() - } - } - - // default if `impl Default`, otherwise - panic - ( &::core::marker::PhantomData::< #ty > ).maybe_default() - } - } - } - Some( default_val ) => - { - qt! - { - ( #default_val ).into() - } - } - }; - - qt! - { - let #ident = if self.storage.#ident.is_some() - { - self.storage.#ident.take().unwrap() - } - else - { - #_else - }; - } - - }; - - Ok( tokens ) -} - -/// -/// Extract name of a field out. -/// - -#[ inline( always ) ] -fn field_name_map( field : &FormerField< '_ > ) -> syn::Ident -{ - field.ident.clone() -} - -/// -/// Generate a former setter for the field. -/// -/// If aliases provided, also generate aliases -/// -/// # Example of output -/// ```ignore -/// #[ doc = "Setter for the 'int_1' field." ] -/// #[ inline ] -/// pub fn int_1< Src >( mut self, src : Src ) -> Self -/// where -/// Src : ::core::convert::Into< i32 >, -/// { -/// debug_assert!( self.int_1.is_none() ); -/// self.storage.int_1 = ::core::option::Option::Some( src.into() ); -/// self -/// } -/// -/// /// #[ doc = "Setter for the 'int_1' field." ] -/// #[ inline ] -/// pub fn int_1_alias< Src >( mut self, src : Src ) -> Self -/// where -/// Src : ::core::convert::Into< i32 >, -/// { -/// debug_assert!( self.int_1.is_none() ); -/// self.storage.int_1 = ::core::option::Option::Some( src.into() ); -/// self -/// } -/// ``` - -#[ inline ] -fn field_setter_map( field : &FormerField< '_ > ) -> Result< TokenStream > -{ - let ident = &field.ident; - - if let Some( setter_attr ) = &field.attrs.setter - { - if !setter_attr.condition.value() - { - return Ok( qt!{ } ); - } - } - - let non_optional_ty = &field.non_optional_ty; - // Either subformer or ordinary setter. - let setter_tokens = if let Some( subformer_ty ) = &field.attrs.subformer - { - subformer_field_setter( ident, ident, non_optional_ty, &subformer_ty.expr ) - // field_setter( ident, ident, non_optional_ty ) - } - else - { - field_setter( ident, ident, non_optional_ty ) - }; - - let r = if let Some( alias_attr ) = &field.attrs.alias - { - let alias_tokens = field_setter( ident, &alias_attr.alias, non_optional_ty ); - let token = qt! - { - #setter_tokens - #alias_tokens - }; - Ok( token ) - } - else - { - Ok( setter_tokens ) - }; - - // tree_print!( r.as_ref().unwrap() ); - r -} - -/// -/// Generate a single setter for the 'field_ident' with the 'setter_name' name. -/// -/// Used as a helper function for field_setter_map(), which generates all alias setters -/// -/// # Example of output -/// ```ignore -/// #[ doc = "Setter for the 'int_1' field." ] -/// #[ inline ] -/// pub fn int_1< Src >( mut self, src : Src ) -> Self -/// where -/// Src : ::core::convert::Into< i32 >, -/// { -/// debug_assert!( self.int_1.is_none() ); -/// self.storage.int_1 = ::core::option::Option::Some( src.into() ); -/// self -/// } -/// ``` - -#[ inline ] -fn field_setter -( - field_ident : &syn::Ident, - setter_name : &syn::Ident, - non_optional_type : &syn::Type, -) --> TokenStream -{ - let doc = format! - ( - "Setter for the '{}' field.", - field_ident, - ); - - qt! - { - #[ doc = #doc ] - #[ inline ] - pub fn #setter_name< Src >( mut self, src : Src ) -> Self - where Src : ::core::convert::Into< #non_optional_type >, - { - debug_assert!( self.storage.#field_ident.is_none() ); - self.storage.#field_ident = ::core::option::Option::Some( src.into() ); - self - } - } -} - -/// -/// Generate a sub-former setter for the 'field_ident' with the 'setter_name' name. -/// -/// # Example of generated code -/// -/// ```ignore -/// pub fn hashmap_strings_1( mut self ) -> former::HashMapSubformer -/// < -/// String, -/// String, -/// std::collections::HashMap< String, String >, -/// Struct1Former, -/// impl Fn( std::collections::HashMap< String, String >, core::option::Option< Self > ) -> Self -/// > -/// { -/// let formed = self.hashmap_strings_1.take(); -/// let on_end = | formed : std::collections::HashMap< String, String >, mut former : core::option::Option< Self > | -> Self -/// { -/// former.hashmap_strings_1 = Some( formed ); -/// former -/// }; -/// former::HashMapSubformer::begin( formed, self, on_end ) -/// } -/// ``` - -#[ inline ] -fn subformer_field_setter -( - field_ident : &syn::Ident, - setter_name : &syn::Ident, - non_optional_type : &syn::Type, - subformer_type : &syn::Type, -) --> TokenStream -{ - let doc = format! - ( - "Subformer setter for the '{}' field.", - field_ident - ); - - // tree_print!( non_optional_type ); - // code_print!( non_optional_type ); - let params = typ::type_parameters( &non_optional_type, .. ); - // params.iter().for_each( | e | println!( "{}", qt!( #e ) ) ); - - qt! - { - #[ doc = #doc ] - #[ inline ] - pub fn #setter_name( mut self ) -> #subformer_type - < - #( #params, )* - #non_optional_type, - Self, - impl Fn( #non_optional_type, core::option::Option< Self > ) -> Self, - > - { - let formed = self.storage.#setter_name.take(); - let on_end = | formed : #non_optional_type, former : core::option::Option< Self > | -> Self - { - let mut former = former.unwrap(); - former.storage.#setter_name = Some( formed ); - former - }; - #subformer_type::begin( formed, Some( self ), on_end ) - } - } - -} - -/// -/// Generate documentation for the former. -/// - -fn doc_generate( name_ident : &syn::Ident ) -> ( String, String ) -{ - - let doc_former_mod = format! - ( -r#" Implementation of former for [{}]. -"#, - name_ident - ); - - let doc_example1 = -r#" -use former::Former; -#[ derive( Former ) ] -pub struct Struct1 -{ - #[default( 31 ) ] - field1 : i32, -} -"#; - - let doc_former_struct = format! - ( -r#" Object to form [{}]. If field's values is not set then default value of the field is set. - -For specifying custom default value use attribute `default`. For example: -``` -{} -``` -"#, - name_ident, doc_example1 - ); - - ( doc_former_mod, doc_former_struct ) -} - -// - -/// -/// Generate parts, used for generating `perform()`` method. -/// -/// Similar to `form()`, but will also invoke function from `perform` attribute, if specified. -/// -/// # Example of returned tokens : -/// -/// ## perform : -/// return result; -/// -/// ## perform_output : -/// < T : ::core::default::Default > -/// -/// ## perform_generics : -/// Vec< T > - -pub fn performer< 'a > -( - name_ident : &syn::Ident, - generics_ty : &syn::TypeGenerics< '_ >, - attrs : impl Iterator< Item = &'a syn::Attribute >, -) --> Result< ( TokenStream, TokenStream, TokenStream ) > -{ - - let mut perform = qt! - { - return result; - }; - let mut perform_output = qt!{ #name_ident #generics_ty }; - let mut perform_generics = qt!{}; - for attr in attrs - { - if let Some( ident ) = attr.path().get_ident() - { - let ident_string = format!( "{}", ident ); - if ident_string == "perform" - { - match attr.meta - { - syn::Meta::List( ref meta_list ) => - { - // default.replace( syn::parse2::< AttributeDefault >( meta_list.tokens.clone() )? ); - // let attr_perform = syn::parse2::< AttributeFormAfter >( attr.tokens.clone() )?; - let attr_perform = syn::parse2::< AttributeFormAfter >( meta_list.tokens.clone() )?; - let signature = &attr_perform.signature; - let generics = &signature.generics; - perform_generics = qt!{ #generics }; - let perform_ident = &signature.ident; - let output = &signature.output; - if let syn::ReturnType::Type( _, boxed_type ) = output - { - perform_output = qt!{ #boxed_type }; - } - perform = qt! - { - return result.#perform_ident(); - }; - }, - _ => return_syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ), - } - } - } - else - { - return_syn_err!( "Unknown structure attribute:\n{}", qt!{ attr } ); - } - } - - Ok( ( perform, perform_output, perform_generics ) ) -} - -// - -/// -/// Generate the whole Former ecosystem -/// -/// Output examples can be found in [docs to former crate](https://docs.rs/former/latest/former/) -/// - -pub fn former( input : proc_macro::TokenStream ) -> Result< TokenStream > -{ - - let original_input = input.clone(); - let ast = match syn::parse::< syn::DeriveInput >( input ) - { - Ok( syntax_tree ) => syntax_tree, - Err( err ) => return Err( err ), - }; - let has_debug = attr::has_debug( ast.attrs.iter() )?; - let example_of_custom_setter = false; - - - /* names */ - - let name_ident = &ast.ident; - let former_name = format!( "{}Former", name_ident ); - let former_name_ident = syn::Ident::new( &former_name, name_ident.span() ); - let former_storage_name = format!( "{}FormerStorage", name_ident ); - let former_storage_name_ident = syn::Ident::new( &former_storage_name, name_ident.span() ); - - /* generic parameters */ - - let generics = &ast.generics; - let ( generics_impl, generics_ty, generics_where ) = generics.split_for_impl(); - let _generics_params = generics::params_names( generics ).params; - let generics_params = if _generics_params.len() == 0 - { - qt!{} - } - else - { - qt!{ #_generics_params, } - }; - - // add embedded generic parameters - let mut extra_generics : syn::Generics = parse_quote! - { - < __FormerContext = #name_ident #generics_ty, __FormerEnd = former::ReturnFormed > - }; - extra_generics.where_clause = parse_quote! - { - where __FormerEnd : former::FormingEnd< #name_ident #generics_ty, __FormerContext >, - }; - // xxx : write helper to fix bug with where - let generics_of_former = generics::merge( &generics, &extra_generics ); - let ( generics_of_former_impl, generics_of_former_ty, generics_of_former_where ) = generics_of_former.split_for_impl(); - let generics_of_former_with_defaults = generics_of_former.params.clone(); - // macro_tools::code_print!( generics_of_former_with_defaults ); - // macro_tools::code_print!( extra_generics ); - - /* structure attribute */ - - let ( perform, perform_output, perform_generics ) = performer - ( - &name_ident, - &generics_ty, - ast.attrs.iter(), - )?; - - /* */ - - let fields = match ast.data - { - syn::Data::Struct( ref data_struct ) => match data_struct.fields - { - syn::Fields::Named( ref fields_named ) => - { - &fields_named.named - }, - _ => return Err( syn_err!( ast, "Unknown format of data, expected syn::Fields::Named( ref fields_named )\n {}", qt!{ #ast } ) ), - }, - _ => return Err( syn_err!( ast, "Unknown format of data, expected syn::Data::Struct( ref data_struct )\n {}", qt!{ #ast } ) ), - }; - - let former_fields : Vec< Result< FormerField< '_ > > > = fields.iter().map( | field | - { - let attrs = Attributes::parse( &field.attrs )?; - let vis = &field.vis; - let ident = field.ident.as_ref() - .ok_or_else( || syn_err!( field, "Expected that each field has key, but some does not:\n {}", qt!{ #field } ) )?; - let colon_token = &field.colon_token; - let ty = &field.ty; - let is_optional = is_optional( ty ); - let of_type = container_kind::of_optional( ty ).0; - let non_optional_ty : &syn::Type = if is_optional { parameter_internal_first( ty )? } else { ty }; - let former_field = FormerField { attrs, vis, ident, colon_token, ty, non_optional_ty, is_optional, of_type }; - Ok( former_field ) - }).collect(); - - let former_fields : Vec< _ > = process_results( former_fields, | iter | iter.collect() )?; - - let ( fields_none, fields_optional, fields_form, fields_names, fields_setter ) - : ( Vec< _ >, Vec< _ >, Vec< _ >, Vec< _ >, Vec< _ > ) - = former_fields.iter().map( | former_field | - {( - field_none_map( former_field ), - field_optional_map( former_field ), - field_form_map( former_field ), - field_name_map( former_field ), - field_setter_map( former_field ), - )}).multiunzip(); - - let ( _doc_former_mod, doc_former_struct ) = doc_generate( name_ident ); - let fields_setter : Vec< _ > = process_results( fields_setter, | iter | iter.collect() )?; - let fields_form : Vec< _ > = process_results( fields_form, | iter | iter.collect() )?; - - let result = qt! - { - - #[ automatically_derived ] - impl #generics_impl #name_ident #generics_ty - #generics_where - { - /// - /// Make former, variation of builder pattern to form structure defining values of fields step by step. - /// - #[ inline( always ) ] - pub fn former() -> #former_name_ident < #generics_params #name_ident #generics_ty, former::ReturnFormed > - { - #former_name_ident :: < #generics_params #name_ident #generics_ty, former::ReturnFormed > :: new() - } - } - - // xxx : rename to storage - #[ doc = "Container of a corresponding former." ] - pub struct #former_storage_name_ident #generics_ty - #generics_where - { - #( - /// A field - #fields_optional, - )* - } - - impl #generics_impl ::core::default::Default for #former_storage_name_ident #generics_ty - #generics_where - { - - #[ inline( always ) ] - fn default() -> Self - { - Self - { - #( #fields_none, )* - } - } - - } - - #[ doc = #doc_former_struct ] - #[ automatically_derived ] - pub struct #former_name_ident < #generics_of_former_with_defaults > - #generics_of_former_where - { - storage : #former_storage_name_ident #generics_ty, - context : core::option::Option< __FormerContext >, - on_end : core::option::Option< __FormerEnd >, - } - - #[ automatically_derived ] - impl #generics_of_former_impl #former_name_ident #generics_of_former_ty - #generics_of_former_where - { - - /// - /// Finish setting options and return formed entity. - /// - /// `perform` has no effect on method `form`, but change behavior and returned type of method `perform`. - /// - #[ inline( always ) ] - pub fn form( mut self ) -> #name_ident #generics_ty - { - #( #fields_form )* - let result = #name_ident - { - #( #fields_names, )* - }; - return result; - } - - /// - /// Finish setting options and call perform on formed entity. - /// - /// If `perform` defined then associated method is called and its result returned instead of entity. - /// For example `perform()` of structure with : `#[ perform( fn after1() -> &str > )` returns `&str`. - /// - #[ inline( always ) ] - pub fn perform #perform_generics ( self ) -> #perform_output - { - let result = self.form(); - #perform - } - - /// - /// Begin the process of forming. Expects context of forming to return it after forming. - /// - #[ inline( always ) ] - pub fn begin - ( - mut storage : core::option::Option< #former_storage_name_ident #generics_ty >, - context : core::option::Option< __FormerContext >, - on_end : __FormerEnd, - ) -> Self - { - if storage.is_none() - { - storage = Some( ::core::default::Default::default() ); - } - Self - { - storage : storage.unwrap(), - context : context, - on_end : ::core::option::Option::Some( on_end ), - } - } - - /// - /// End the process of forming returning original context of forming. - /// - #[ inline( always ) ] - pub fn end( mut self ) -> __FormerContext - { - let on_end = self.on_end.take().unwrap(); - let context = self.context.take(); - let storage = self.form(); - on_end.call( storage, context ) - } - - #( - #fields_setter - )* - - } - - #[ automatically_derived ] - impl #generics_impl #former_name_ident < #generics_params #name_ident #generics_ty, former::ReturnFormed > - #generics_where - { - - /// - /// Construct new instance of former with default parameters. - /// - #[ inline( always ) ] - pub fn new() -> Self - { - // #former_name_ident :: < #generics_params #name_ident #generics_ty, former::ReturnFormed > :: begin - Self :: begin - ( - None, - None, - former::ReturnFormed, - ) - } - - } - - }; - - if has_debug - { - diag::debug_report_print( "derive : Former", original_input, &result ); - } - - // xxx : implement - if example_of_custom_setter - { - let _example = -r#" -impl< Context, End > UserProfileFormer< Context, End > -where - End : former::FormingEnd< UserProfile, Context >, -{ - pub fn age< Src >( mut self, src : Src ) -> Self - where - Src : Into< i32 >, - { - debug_assert!( self.age.is_none() ); - self.storage.age = ::core::option::Option::Some( src.into() ); - self - } -} -"#; - } - - Ok( result ) -} - -// xxx : explain concept of Storage diff --git a/module/core/former_meta/src/derive_former.rs b/module/core/former_meta/src/derive_former.rs new file mode 100644 index 0000000000..b934d9cbf9 --- /dev/null +++ b/module/core/former_meta/src/derive_former.rs @@ -0,0 +1,777 @@ + +use super::*; +use iter_tools::{ Itertools, process_results }; +use macro_tools::{ attr, diag, generic_params, generic_args, typ, derive, Result }; +use proc_macro2::TokenStream; + +// qqq : implement interfaces for other containers + +mod field; +use field::*; +mod field_attrs; +use field_attrs::*; +mod struct_attrs; +use struct_attrs::*; + +/// Generates the code for implementing the `FormerMutator` trait for a specified former definition type. +/// +/// This function generate code that implements the `FormerMutator` trait based on the given +/// former definition types and their associated generics. The `FormerMutator` trait provides the +/// functionality to mutate the storage and context of an entity just before its formation process +/// completes. This is particularly useful for performing final adjustments or validations on the data +/// before the entity is fully constructed. +/// +/// # Example +/// +/// Below is an example of how the generated code might look: +/// +/// ```rust, ignore +/// impl< Context, Formed > former::FormerMutator +/// for Struct1FormerDefinitionTypes< Context, Formed > +/// { +/// /// Mutates the context and storage of the entity just before the formation process completes. +/// #[ inline ] +/// fn form_mutation( storage : &mut Self::Storage, _context : &mut ::core::option::Option< Self::Context > ) +/// { +/// storage.a.get_or_insert_with( Default::default ); +/// storage.b.get_or_insert_with( Default::default ); +/// storage.c = Some( format!( "{:?} - {}", storage.a.unwrap(), storage.b.as_ref().unwrap() ) ); +/// } +/// } +/// ``` +/// + +pub fn mutator +( + mutator : &AttributeMutator, + former_definition_types : &syn::Ident, + former_definition_types_generics_impl : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + former_definition_types_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + former_definition_types_generics_where : &syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, +) +-> Result< TokenStream > +{ + let former_mutator_code = if mutator.custom + { + qt!{} + } + else + { + qt! + { + impl< #former_definition_types_generics_impl > former::FormerMutator + for #former_definition_types < #former_definition_types_generics_ty > + where + #former_definition_types_generics_where + { + } + } + }; + + if mutator.hint + { + let hint = format! + ( + r#" += Example of custom mutator + +impl< {} > former::FormerMutator +for {} < {} > +where + {} +{{ + /// Mutates the context and storage of the entity just before the formation process completes. + #[ inline ] + fn form_mutation( storage : &mut Self::Storage, context : &mut Option< Self::Context > ) + {{ + }} +}} + "#, + format!( "{}", qt!{ #former_definition_types_generics_impl } ), + former_definition_types, + format!( "{}", qt!{ #former_definition_types_generics_ty } ), + format!( "{}", qt!{ #former_definition_types_generics_where } ), + ); + println!( "{hint}" ); + }; + + Ok( former_mutator_code ) +} + +/// +/// Generate documentation for the former. +/// + +fn doc_generate( stru : &syn::Ident ) -> ( String, String ) +{ + + let doc_former_mod = format! + ( +r#" Implementation of former for [{}]. +"#, + stru + ); + + let doc_former_struct = format! + ( +r#" +Structure to form [{}]. Represents a forming entity designed to construct objects through a builder pattern. + +This structure holds temporary storage and context during the formation process and +utilizes a defined end strategy to finalize the object creation. +"#, + stru + ); + + ( doc_former_mod, doc_former_struct ) +} + +/// +/// Generate the whole Former ecosystem +/// +/// Output examples can be found in [docs to former crate](https://docs.rs/former/latest/former/) +/// + +pub fn former( input : proc_macro::TokenStream ) -> Result< TokenStream > +{ + use macro_tools::IntoGenericArgs; + + let original_input = input.clone(); + let ast = match syn::parse::< syn::DeriveInput >( input ) + { + Ok( syntax_tree ) => syntax_tree, + Err( err ) => return Err( err ), + }; + let has_debug = attr::has_debug( ast.attrs.iter() )?; + let struct_attrs = StructAttributes::from_attrs( ast.attrs.iter() )?; + + /* names */ + + let vis = &ast.vis; + let stru = &ast.ident; + let former_name = format!( "{}Former", stru ); + let former = syn::Ident::new( &former_name, stru.span() ); + let former_storage_name = format!( "{}FormerStorage", stru ); + let former_storage = syn::Ident::new( &former_storage_name, stru.span() ); + let former_definition_name = format!( "{}FormerDefinition", stru ); + let former_definition = syn::Ident::new( &former_definition_name, stru.span() ); + let former_definition_types_name = format!( "{}FormerDefinitionTypes", stru ); + let former_definition_types = syn::Ident::new( &former_definition_types_name, stru.span() ); + let as_subformer_name = format!( "{}AsSubformer", stru ); + let as_subformer = syn::Ident::new( &as_subformer_name, stru.span() ); + let as_subformer_end_name = format!( "{}AsSubformerEnd", stru ); + let as_subformer_end = syn::Ident::new( &as_subformer_end_name, stru.span() ); + + let as_subformer_end_doc = format! + ( + r#" +Represents an end condition for former of [`${stru}`], tying the lifecycle of forming processes to a broader context. + +This trait is intended for use with subformer alias, ensuring that end conditions are met according to the +specific needs of the broader forming context. It mandates the implementation of `former::FormingEnd`. + "# + ); + + /* parameters for structure */ + + let generics = &ast.generics; + let ( struct_generics_with_defaults, struct_generics_impl, struct_generics_ty, struct_generics_where ) + = generic_params::decompose( generics ); + + /* parameters for definition */ + + let extra : macro_tools::syn::AngleBracketedGenericArguments = parse_quote! + { + < (), #stru < #struct_generics_ty >, former::ReturnPreformed > + }; + let former_definition_args = generic_args::merge( &generics.into_generic_args(), &extra.into() ).args; + + /* parameters for former */ + + let extra : macro_tools::GenericsWithWhere = parse_quote! + { + < Definition = #former_definition < #former_definition_args > > + where + Definition : former::FormerDefinition< Storage = #former_storage < #struct_generics_ty > >, + Definition::Types : former::FormerDefinitionTypes< Storage = #former_storage < #struct_generics_ty > >, + }; + let extra = generic_params::merge( &generics, &extra.into() ); + + let ( former_generics_with_defaults, former_generics_impl, former_generics_ty, former_generics_where ) + = generic_params::decompose( &extra ); + + /* parameters for former perform */ + + let extra : macro_tools::GenericsWithWhere = parse_quote! + { + < Definition = #former_definition < #former_definition_args > > + where + Definition : former::FormerDefinition + < + Storage = #former_storage < #struct_generics_ty >, + Formed = #stru < #struct_generics_ty >, + >, + Definition::Types : former::FormerDefinitionTypes + < + Storage = #former_storage < #struct_generics_ty >, + Formed = #stru < #struct_generics_ty >, + >, + }; + let extra = generic_params::merge( &generics, &extra.into() ); + + let ( _former_perform_generics_with_defaults, former_perform_generics_impl, former_perform_generics_ty, former_perform_generics_where ) + = generic_params::decompose( &extra ); + + /* parameters for definition types */ + + let extra : macro_tools::GenericsWithWhere = parse_quote! + { + < __Context = (), __Formed = #stru < #struct_generics_ty > > + }; + let former_definition_types_generics = generic_params::merge( &generics, &extra.into() ); + let ( former_definition_types_generics_with_defaults, former_definition_types_generics_impl, former_definition_types_generics_ty, former_definition_types_generics_where ) + = generic_params::decompose( &former_definition_types_generics ); + + let former_definition_types_phantom = macro_tools::phantom::tuple( &former_definition_types_generics_impl ); + + /* parameters for definition */ + + let extra : macro_tools::GenericsWithWhere = parse_quote! + { + < __Context = (), __Formed = #stru < #struct_generics_ty >, __End = former::ReturnPreformed > + }; + let generics_of_definition = generic_params::merge( &generics, &extra.into() ); + let ( former_definition_generics_with_defaults, former_definition_generics_impl, former_definition_generics_ty, former_definition_generics_where ) + = generic_params::decompose( &generics_of_definition ); + + let former_definition_phantom = macro_tools::phantom::tuple( &former_definition_generics_impl ); + + /* struct attributes */ + + let ( _doc_former_mod, doc_former_struct ) = doc_generate( stru ); + let ( perform, perform_output, perform_generics ) = struct_attrs.performer()?; + + /* fields */ + + let fields = derive::named_fields( &ast )?; + + let formed_fields : Vec< Result< FormerField< '_ > > > = fields + .into_iter() + .map( | field | + { + FormerField::from_syn( field, true, true ) + }) + .collect(); + let formed_fields : Vec< _ > = process_results( formed_fields, | iter | iter.collect() )?; + + let storage_fields : Vec< Result< FormerField< '_ > > > = struct_attrs + .storage_fields() + .iter() + .map( | field | + { + FormerField::from_syn( &field, true, false ) + }) + .collect(); + let storage_fields : Vec< _ > = process_results( storage_fields, | iter | iter.collect() )?; + + let + ( + storage_field_none, + storage_field_optional, + storage_field_name, + storage_field_preform, + former_field_setter, + ) + : + ( Vec< _ >, Vec< _ >, Vec< _ >, Vec< _ >, Vec< _ > ) + = formed_fields + .iter() + .chain( storage_fields.iter() ) + .map( | field | + {( + field.storage_fields_none(), + field.storage_field_optional(), + field.storage_field_name(), + field.storage_field_preform(), + field.former_field_setter + ( + &stru, + &struct_generics_impl, + &struct_generics_ty, + &struct_generics_where, + &former, + &former_generics_impl, + &former_generics_ty, + &former_generics_where, + &former_storage, + &original_input, + ), + )}).multiunzip(); + + let results : Result< Vec< _ > > = former_field_setter.into_iter().collect(); + let ( former_field_setter, namespace_code ) : ( Vec< _ >, Vec< _ > ) = results?.into_iter().unzip(); + + let storage_field_preform : Vec< _ > = process_results( storage_field_preform, | iter | iter.collect() )?; + + let former_mutator_code = mutator + ( + &struct_attrs.mutator, + &former_definition_types, + &former_definition_types_generics_impl, + &former_definition_types_generics_ty, + &former_definition_types_generics_where, + )?; + + let result = qt! + { + + // = formed + + #[ automatically_derived ] + impl < #struct_generics_impl > #stru < #struct_generics_ty > + where + #struct_generics_where + { + + /// + /// Provides a mechanism to initiate the formation process with a default completion behavior. + /// + + #[ inline( always ) ] + pub fn former() -> #former < #struct_generics_ty #former_definition< #former_definition_args > > + { + #former :: < #struct_generics_ty #former_definition< #former_definition_args > > :: new_coercing( former::ReturnPreformed ) + } + + } + + // = entity to former + + impl< #struct_generics_impl Definition > former::EntityToFormer< Definition > + for #stru < #struct_generics_ty > + where + Definition : former::FormerDefinition< Storage = #former_storage < #struct_generics_ty > >, + #struct_generics_where + { + type Former = #former < #struct_generics_ty Definition > ; + } + + impl< #struct_generics_impl > former::EntityToStorage + for #stru < #struct_generics_ty > + where + #struct_generics_where + { + type Storage = #former_storage < #struct_generics_ty >; + } + + impl< #struct_generics_impl __Context, __Formed, __End > former::EntityToDefinition< __Context, __Formed, __End > + for #stru < #struct_generics_ty > + where + __End : former::FormingEnd< #former_definition_types < #struct_generics_ty __Context, __Formed > >, + #struct_generics_where + { + type Definition = #former_definition < #struct_generics_ty __Context, __Formed, __End >; + type Types = #former_definition_types < #struct_generics_ty __Context, __Formed >; + } + + impl< #struct_generics_impl __Context, __Formed > former::EntityToDefinitionTypes< __Context, __Formed > + for #stru < #struct_generics_ty > + where + #struct_generics_where + { + type Types = #former_definition_types < #struct_generics_ty __Context, __Formed >; + } + + // = definition types + + /// Defines the generic parameters for formation behavior including context, form, and end conditions. + #[ derive( Debug ) ] + #vis struct #former_definition_types < #former_definition_types_generics_with_defaults > + where + #former_definition_types_generics_where + { + // _phantom : core::marker::PhantomData< ( __Context, __Formed ) >, + _phantom : #former_definition_types_phantom, + } + + impl < #former_definition_types_generics_impl > ::core::default::Default + for #former_definition_types < #former_definition_types_generics_ty > + where + #former_definition_types_generics_where + { + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + } + + impl < #former_definition_types_generics_impl > former::FormerDefinitionTypes + for #former_definition_types < #former_definition_types_generics_ty > + where + #former_definition_types_generics_where + { + type Storage = #former_storage < #struct_generics_ty >; + type Formed = __Formed; + type Context = __Context; + } + + // = definition + + /// Holds the definition types used during the formation process. + #[ derive( Debug ) ] + #vis struct #former_definition < #former_definition_generics_with_defaults > + where + #former_definition_generics_where + { + // _phantom : core::marker::PhantomData< ( __Context, __Formed, __End ) >, + _phantom : #former_definition_phantom, + } + + impl < #former_definition_generics_impl > ::core::default::Default + for #former_definition < #former_definition_generics_ty > + where + #former_definition_generics_where + { + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + } + + impl < #former_definition_generics_impl > former::FormerDefinition + for #former_definition < #former_definition_generics_ty > + where + __End : former::FormingEnd< #former_definition_types < #former_definition_types_generics_ty > >, + #former_definition_generics_where + { + type Types = #former_definition_types < #former_definition_types_generics_ty >; + type End = __End; + type Storage = #former_storage < #struct_generics_ty >; + type Formed = __Formed; + type Context = __Context; + } + + // = former mutator + + #former_mutator_code + + // = storage + + #[ doc = "Stores potential values for fields during the formation process." ] + #[ allow( explicit_outlives_requirements ) ] + #vis struct #former_storage < #struct_generics_with_defaults > + where + #struct_generics_where + { + #( + /// A field + #storage_field_optional, + )* + } + + impl < #struct_generics_impl > ::core::default::Default + for #former_storage < #struct_generics_ty > + where + #struct_generics_where + { + + #[ inline( always ) ] + fn default() -> Self + { + Self + { + #( #storage_field_none, )* + } + } + + } + + impl < #struct_generics_impl > former::Storage + for #former_storage < #struct_generics_ty > + where + #struct_generics_where + { + type Preformed = #stru < #struct_generics_ty >; + } + + impl < #struct_generics_impl > former::StoragePreform + for #former_storage < #struct_generics_ty > + where + #struct_generics_where + { + // type Preformed = #stru < #struct_generics_ty >; + + fn preform( mut self ) -> Self::Preformed + { + #( #storage_field_preform )* + // Rust does not support that, yet + // let result = < Definition::Types as former::FormerDefinitionTypes >::Formed + let result = #stru :: < #struct_generics_ty > + { + #( #storage_field_name )* + // #( #storage_field_name, )* + }; + return result; + } + + } + + // = former + + #[ doc = #doc_former_struct ] + #vis struct #former < #former_generics_with_defaults > + where + #former_generics_where + { + /// Temporary storage for all fields during the formation process. It contains + /// partial data that progressively builds up to the final object. + pub storage : Definition::Storage, + /// An optional context providing additional data or state necessary for custom + /// formation logic or to facilitate this former's role as a subformer within another former. + pub context : core::option::Option< Definition::Context >, + /// An optional closure or handler that is invoked to transform the accumulated + /// temporary storage into the final object structure once formation is complete. + pub on_end : core::option::Option< Definition::End >, + } + + #[ automatically_derived ] + impl < #former_generics_impl > #former < #former_generics_ty > + where + #former_generics_where + { + + /// + /// Initializes a former with an end condition and default storage. + /// + #[ inline( always ) ] + pub fn new( on_end : Definition::End ) -> Self + { + Self::begin_coercing( None, None, on_end ) + } + + /// + /// Initializes a former with a coercible end condition. + /// + #[ inline( always ) ] + pub fn new_coercing< IntoEnd >( end : IntoEnd ) -> Self + where + IntoEnd : Into< Definition::End >, + { + Self::begin_coercing + ( + None, + None, + end, + ) + } + + /// + /// Begins the formation process with specified context and termination logic. + /// + #[ inline( always ) ] + pub fn begin + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : < Definition as former::FormerDefinition >::End, + ) + -> Self + { + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some( on_end ), + } + } + + /// + /// Starts the formation process with coercible end condition and optional initial values. + /// + #[ inline( always ) ] + pub fn begin_coercing< IntoEnd > + ( + mut storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : IntoEnd, + ) -> Self + where + IntoEnd : ::core::convert::Into< < Definition as former::FormerDefinition >::End >, + { + if storage.is_none() + { + storage = Some( ::core::default::Default::default() ); + } + Self + { + storage : storage.unwrap(), + context : context, + on_end : ::core::option::Option::Some( ::core::convert::Into::into( on_end ) ), + } + } + + /// + /// Wrapper for `end` to align with common builder pattern terminologies. + /// + #[ inline( always ) ] + pub fn form( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + self.end() + } + + /// + /// Completes the formation and returns the formed object. + /// + #[ inline( always ) ] + pub fn end( mut self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + let on_end = self.on_end.take().unwrap(); + let mut context = self.context.take(); + < Definition::Types as former::FormerMutator >::form_mutation( &mut self.storage, &mut context ); + former::FormingEnd::< Definition::Types >::call( &on_end, self.storage, context ) + } + + #( + #former_field_setter + )* + + } + + // = former :: preform + + impl< #former_generics_impl > #former< #former_generics_ty > + where + Definition : former::FormerDefinition< Storage = #former_storage < #struct_generics_ty >, Formed = #stru < #struct_generics_ty > >, + Definition::Types : former::FormerDefinitionTypes< Storage = #former_storage < #struct_generics_ty >, Formed = #stru < #struct_generics_ty > >, + #former_generics_where + { + + /// Executes the transformation from the former's storage state to the preformed object as specified by the definition. + pub fn preform( self ) -> < Definition::Types as former::FormerDefinitionTypes >::Formed + { + former::StoragePreform::preform( self.storage ) + } + + } + + // = former :: perform + + #[ automatically_derived ] + impl < #former_perform_generics_impl > #former < #former_perform_generics_ty > + where + #former_perform_generics_where + { + + /// + /// Finish setting options and call perform on formed entity. + /// + /// If `perform` defined then associated method is called and its result returned instead of entity. + /// For example `perform()` of structure with : `#[ perform( fn after1() -> &str > )` returns `&str`. + /// + #[ inline( always ) ] + pub fn perform #perform_generics ( self ) -> #perform_output + { + let result = self.form(); + #perform + } + + } + + // = former begin + + impl< #struct_generics_impl Definition > former::FormerBegin< Definition > + // for ChildFormer< Definition > + for #former + < + #struct_generics_ty + Definition, + > + where + Definition : former::FormerDefinition< Storage = #former_storage < #struct_generics_ty > >, + #struct_generics_where + { + + #[ inline( always ) ] + fn former_begin + ( + storage : core::option::Option< Definition::Storage >, + context : core::option::Option< Definition::Context >, + on_end : Definition::End, + ) + -> Self + { + debug_assert!( storage.is_none() ); + Self::begin( None, context, on_end ) + } + + } + + // = subformer + + /// Provides a specialized former for structure using predefined settings for superformer and end conditions. + /// + /// This type alias configures former of the structure with a specific definition to streamline its usage in broader contexts, + /// especially where structure needs to be integrated into larger structures with a clear termination condition. + #vis type #as_subformer < #struct_generics_ty __Superformer, __End > = #former + < + #struct_generics_ty + #former_definition + < + #struct_generics_ty + __Superformer, + __Superformer, + __End, + // impl former::FormingEnd< CommandFormerDefinitionTypes< K, __Superformer, __Superformer > >, + >, + >; + + // = as subformer end + + #[ doc = #as_subformer_end_doc ] + pub trait #as_subformer_end < #struct_generics_impl SuperFormer > + where + #struct_generics_where + Self : former::FormingEnd + < + #former_definition_types < #struct_generics_ty SuperFormer, SuperFormer >, + >, + { + } + + impl< #struct_generics_impl SuperFormer, __T > #as_subformer_end < #struct_generics_ty SuperFormer > + for __T + where + #struct_generics_where + Self : former::FormingEnd + < + #former_definition_types < #struct_generics_ty SuperFormer, SuperFormer >, + >, + { + } + + // = etc + + #( + #namespace_code + )* + + }; + + if has_debug + { + let about = format!( "derive : Former\nstructure : {stru}" ); + diag::report_print( about, &original_input, &result ); + } + + Ok( result ) +} diff --git a/module/core/former_meta/src/derive_former/field.rs b/module/core/former_meta/src/derive_former/field.rs new file mode 100644 index 0000000000..a38cdead95 --- /dev/null +++ b/module/core/former_meta/src/derive_former/field.rs @@ -0,0 +1,1186 @@ + +use super::*; +use macro_tools::{ container_kind }; + +/// +/// Definition of a field. +/// + +#[ allow( dead_code ) ] +pub struct FormerField< 'a > +{ + pub attrs : FieldAttributes, + pub vis : &'a syn::Visibility, + pub ident : &'a syn::Ident, + pub colon_token : &'a Option< syn::token::Colon >, + pub ty : &'a syn::Type, + pub non_optional_ty : &'a syn::Type, + pub is_optional : bool, + pub of_type : container_kind::ContainerKind, + pub for_storage : bool, + pub for_formed : bool, +} + +impl< 'a > FormerField< 'a > +{ + +/** methods + +from_syn + +storage_fields_none +storage_field_optional +storage_field_preform +storage_field_name +former_field_setter +subform_setter +container_setter +scalar_setter + +scalar_setter_name +container_setter_name +subform_setter_name +scalar_setter_required + +*/ + + /// Construct former field from [`syn::Field`] + pub fn from_syn( field : &'a syn::Field, for_storage : bool, for_formed : bool ) -> Result< Self > + { + let attrs = FieldAttributes::from_attrs( field.attrs.iter() )?; + let vis = &field.vis; + let ident = field.ident.as_ref() + .ok_or_else( || syn_err!( field, "Expected that each field has key, but some does not:\n {}", qt!{ #field } ) )?; + let colon_token = &field.colon_token; + let ty = &field.ty; + let is_optional = typ::is_optional( ty ); + let of_type = container_kind::of_optional( ty ).0; + let non_optional_ty : &syn::Type = if is_optional { typ::parameter_first( ty )? } else { ty }; + let field2 = Self + { + attrs, + vis, + ident, + colon_token, + ty, + non_optional_ty, + is_optional, + of_type, + for_storage, + for_formed, + }; + Ok( field2 ) + } + + /// + /// Generate fields for initializer of a struct setting each field to `None`. + /// + /// Used for initializing a Container, where on initialization all fields are None. User can alter them through builder pattern + /// + /// ### Basic use-case. of output + /// + /// ```ignore + /// int_1 : core::option::Option::None, + /// string_1 : core::option::Option::None, + /// int_optional_1 : core::option::Option::None, + /// ``` + /// + + #[ inline( always ) ] + pub fn storage_fields_none( &self ) -> TokenStream + { + let ident = Some( self.ident.clone() ); + let tokens = qt! { ::core::option::Option::None }; + let ty2 : syn::Type = syn::parse2( tokens ).unwrap(); + + qt! + { + #ident : #ty2 + } + } + + /// + /// Generate field of the former for a field of the structure + /// + /// Used to generate a Container + /// + /// ### Basic use-case. of output + /// + /// ```ignore + /// pub int_1 : core::option::Option< i32 >, + /// pub string_1 : core::option::Option< String >, + /// pub int_optional_1 : core::option::Option< i32 >, + /// pub string_optional_1 : core::option::Option< String >, + /// ``` + /// + + #[ inline( always ) ] + pub fn storage_field_optional( &self ) -> TokenStream + { + let ident = Some( self.ident.clone() ); + let ty = self.ty.clone(); + + // let ty2 = if is_optional( &ty ) + let ty2 = if self.is_optional + { + qt! { #ty } + } + else + { + qt! { ::core::option::Option< #ty > } + }; + + qt! + { + pub #ident : #ty2 + } + + } + + /// + /// Generate code converting a field of the former to the field of the structure. + /// + /// In simple terms, used on `form()` call to unwrap contained values from the former's storage. + /// Will try to use default values if no values supplied by the former and the type implements `Default` trait. + /// + /// ### Generated code will look similar to this : + /// + /// ```ignore + /// let int_1 : i32 = if self.storage.int_1.is_some() + /// { + /// // if int_1 is optional + /// Some( self.storage.int_1.take().unwrap() ) + /// + /// // if int_1 isn't optional + /// self.storage.int_1.take().unwrap() + /// } + /// else + /// { + /// // if int_1 is optional and has default + /// Some( i32::default().into() ) + /// + /// // if int_1 is optional and doesn't have default + /// None + /// + /// // if int_1 isn't optional and has default + /// i32::default().into() + /// + /// // if int_1 isn't optional and hasn't default + /// panic!( "Field 'int_1' isn't initialized" ) + /// }; + /// ``` + /// + + #[ inline( always ) ] + pub fn storage_field_preform( &self ) -> Result< TokenStream > + { + + if !self.for_formed + { + return Ok( qt!{} ) + } + + let ident = self.ident; + let ty = self.ty; + let default : Option< &syn::Expr > = self.attrs.config.as_ref() + .and_then( | attr | attr.default.as_ref() ); + + let tokens = if self.is_optional + { + + let _else = match default + { + None => + { + qt! + { + ::core::option::Option::None + } + } + + Some( default_val ) => + { + qt! + { + ::core::option::Option::Some( ::core::convert::Into::into( #default_val ) ) + } + } + }; + + qt! + { + let #ident = if self.#ident.is_some() + { + ::core::option::Option::Some( self.#ident.take().unwrap() ) + } + else + { + #_else + }; + } + + } + else + { + + let _else = match default + { + None => + { + let panic_msg = format!( "Field '{}' isn't initialized", ident ); + qt! + { + { + // By hardly utilizing deref coercion, we achieve conditional trait implementation + trait MaybeDefault< T > + { + fn maybe_default( self : &Self ) -> T { panic!( #panic_msg ) } + } + + // Panic on non-`Default` types + impl< T > MaybeDefault< T > + for &::core::marker::PhantomData< T > + {} + + // Return default value on `Default`` types + impl< T > MaybeDefault< T > + for ::core::marker::PhantomData< T > + where T : ::core::default::Default, + { + fn maybe_default( self : &Self ) -> T + { + T::default() + } + } + + // default if `impl Default`, otherwise - panic + ( &::core::marker::PhantomData::< #ty > ).maybe_default() + } + } + } + Some( default_val ) => + { + qt! + { + ::core::convert::Into::into( #default_val ) + } + } + }; + + qt! + { + let #ident = if self.#ident.is_some() + { + self.#ident.take().unwrap() + } + else + { + #_else + }; + } + + }; + + Ok( tokens ) + } + + /// + /// Extract name of a field out. + /// + + #[ inline( always ) ] + pub fn storage_field_name( &self ) -> TokenStream + { + + if !self.for_formed + { + return qt!{} + } + + let ident = self.ident; + qt!{ #ident, } + + } + + /// Generates former setters for the specified field within a struct or enum. + /// + /// This function is responsible for dynamically creating code that allows for the building + /// or modifying of fields within a `Former`-enabled struct or enum. It supports different + /// types of setters based on the field attributes, such as scalar setters, container setters, + /// and subform setters. + /// + /// # Returns + /// + /// Returns a pair of `TokenStream` instances: + /// - The first `TokenStream` contains the generated setter functions for the field. + /// - The second `TokenStream` includes additional namespace or supporting code that might + /// be required for the setters to function correctly, such as definitions for end conditions + /// or callbacks used in the formation process. + /// + /// The generation of setters is dependent on the attributes of the field: + /// - **Scalar Setters**: Created for basic data types and simple fields. + /// - **Container Setters**: Generated when the field is annotated to behave as a container, + /// supporting operations like adding or replacing elements. + /// - **Subform Setters**: Generated for fields annotated as subforms, allowing for nested + /// forming processes where a field itself can be formed using a dedicated former. + /// + + #[ inline ] + pub fn former_field_setter + ( + &self, + stru : &syn::Ident, + struct_generics_impl : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + struct_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + struct_generics_where : &syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, + former : &syn::Ident, + former_generics_impl : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + former_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + former_generics_where : &syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, + former_storage : &syn::Ident, + original_input : &proc_macro::TokenStream, + ) + -> Result< ( TokenStream, TokenStream ) > + { + let namespace_code = qt! {}; + let setters_code = self.scalar_setter + ( + former, + former_storage, + ); + + // container setter + let ( setters_code, namespace_code ) = if let Some( _ ) = &self.attrs.container + { + let ( setters_code2, namespace_code2 ) = self.container_setter + ( + stru, + former, + former_storage, + former_generics_impl, + former_generics_ty, + former_generics_where, + original_input, + )?; + ( qt! { #setters_code #setters_code2 }, qt! { #namespace_code #namespace_code2 } ) + } + else + { + ( setters_code, namespace_code ) + }; + + // subform setter + let ( setters_code, namespace_code ) = if self.attrs.subform.is_some() + { + let ( setters_code2, namespace_code2 ) = self.subform_setter + ( + stru, + former, + former_storage, + former_generics_ty, + struct_generics_impl, + struct_generics_ty, + struct_generics_where, + )?; + ( qt! { #setters_code #setters_code2 }, qt! { #namespace_code #namespace_code2 } ) + } + else + { + ( setters_code, namespace_code ) + }; + + // tree_print!( setters_code.as_ref().unwrap() ); + Ok( ( setters_code, namespace_code ) ) + } + + /// Generates setter functions for subforms within a container structure in a builder pattern. + /// + /// This function is a key component of the `former` crate's capability to dynamically create setters for manipulating + /// data within a nested container structure like a `HashMap` or a `Vec`. The setters facilitate the addition or + /// modification of entries within the container, directly from the parent former's context. + /// + /// See `examples/subformer_subform_manual.rs` for example of generated code. + /// + + #[ inline ] + pub fn subform_setter + ( + &self, + stru : &syn::Ident, + former : &syn::Ident, + former_storage : &syn::Ident, + former_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + struct_generics_impl : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + struct_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + struct_generics_where : &syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, + ) + -> Result< ( TokenStream, TokenStream ) > + { + + // if self.attrs.subform.is_none() + // { + // return Ok( qt!{ } ); + // } + + use convert_case::{ Case, Casing }; + let field_ident = self.ident; + let field_typ = self.non_optional_ty; + let attr = self.attrs.subform.as_ref().unwrap(); + // let params = typ::type_parameters( &self.non_optional_ty, .. ); + + // example : `child` + let setter_name = self.subform_setter_name(); + + // example : `ParentFormerAddChildrenEnd`` + let former_add_end_name = format!( "{}FormerAdd{}End", stru, field_ident.to_string().to_case( Case::Pascal ) ); + let former_add_end = syn::Ident::new( &former_add_end_name, field_ident.span() ); + + // example : `_children_former` + let field_add_name = format!( "_{}_add", field_ident ); + let field_add = syn::Ident::new( &field_add_name, field_ident.span() ); + + let doc = format! + ( + r#" + +Initiates the addition of {field_ident} to the `{stru}` entity using a dedicated subformer. + +This method configures and returns a subformer specialized for the `{0}` entities' formation process, +which is part of the `{stru}` entity's construction. The subformer is set up with a specific end condition +handled by `{former_add_end}`, ensuring that the {field_ident} are properly integrated into the +parent's structure once formed. + +# Returns + +Returns an instance of `Former2`, a subformer ready to begin the formation process for `{0}` entities, +allowing for dynamic and flexible construction of the `{stru}` entity's {field_ident}. + + "#, + format!( "{}", qt!{ #field_typ } ), + ); + + let setters_code = qt! + { + + #[ doc = #doc ] + #[ inline( always ) ] + pub fn #field_add< Former2, Definition2 >( self ) -> Former2 + where + Definition2 : former::FormerDefinition + < + End = #former_add_end< Definition >, + Storage = < < #field_typ as former::Container >::Val as former::EntityToStorage >::Storage, + Formed = Self, + Context = Self, + >, + Definition2::Types : former::FormerDefinitionTypes + < + Storage = < < #field_typ as former::Container >::Val as former::EntityToStorage >::Storage, + Formed = Self, + Context = Self, + >, + Former2 : former::FormerBegin< Definition2 >, + { + Former2::former_begin( None, Some( self ), #former_add_end::default() ) + } + + }; + + let setters_code = if attr.setter() + { + + let doc = format! + ( + r#" +Provides a user-friendly interface to add an instancce of {field_ident} to the {stru}. + +# Returns + +Returns an instance of `Former2`, a subformer ready to begin the formation process for `{0}` entities, +allowing for dynamic and flexible construction of the `{stru}` entity's {field_ident}. + + "#, + format!( "{}", qt!{ #field_typ } ), + ); + + qt! + { + #setters_code + + #[ doc = #doc ] + #[ inline( always ) ] + pub fn #setter_name( self ) -> + < < #field_typ as former::Container >::Val as former::EntityToFormer + < + < + < #field_typ as former::Container >::Val as former::EntityToDefinition< Self, Self, #former_add_end < Definition > > + >::Definition, + > + >::Former + // #as_subformer< Self, impl #as_subformer_end< Self > > + { + self.#field_add + ::< < < #field_typ as former::Container >::Val as former::EntityToFormer< _ > >::Former, _, >() + // ::< #former< _ >, _, >() + } + } + + // #[ inline( always ) ] + // pub fn child( self ) -> + // ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + // { + // self._children_add + // ::< < Child as former::EntityToFormer< _ > >::Former, _, >() + // } + + } + else + { + setters_code + }; + + if attr.hint + { + let hint = format! + ( + r#" + +/// Initializes and configures a subformer for adding named child entities. This method leverages an internal function +/// to create and return a configured subformer instance. It allows for the dynamic addition of children with specific names, +/// integrating them into the formation process of the parent entity. + +impl< Definition > {}< Definition > +where + Definition : former::FormerDefinition< Storage = {} >, +{{ + + #[ inline( always ) ] + pub fn {}( self ) -> ChildAsSubformer< Self, impl ChildAsSubformerEnd< Self > > + {{ + self.{}::< ChildFormer< _ >, _, >() + }} + // Replace Child with name of type of element value. + +}} + "#, + former, + former_storage, + field_ident, + field_add_name, + ); + println!( "{hint}" ); + } + + let doc = format! + ( + r#" + +Implements the `FormingEnd` trait for `{former_add_end}` to handle the final +stage of the forming process for a `{stru}` container that contains `{0}` elements. + +This implementation is tailored to manage the transition of {field_ident} elements from a substorage +temporary state into their final state within the `{stru}`'s storage. The function ensures +that the `{stru}`'s {field_ident} storage is initialized if not already set, and then adds the +preformed elements to this storage. + +# Type Parameters + +- `Types2`: Represents the specific types associated with the `Former` trait being applied, + which include storage, formed type, and context. +- `Definition`: Defines the `FormerDefinition` that outlines the storage structure and + the end conditions for the formation process. + +# Parameters + +- `substorage`: The storage from which {field_ident} elements are preformed and retrieved. +- `super_former`: An optional context which, upon invocation, contains the `{former}` + instance being formed. + +# Returns + +Returns the updated `{former}` instance with newly added {field_ident}, completing the +formation process of the `{stru}`. + + "#, + format!( "{}", qt!{ #field_typ } ), + ); + + + let namespace_code = qt! + { + + #[ doc = #doc ] + pub struct #former_add_end< Definition > + { + _phantom : core::marker::PhantomData< fn( Definition ) >, + } + + impl< Definition > Default + for #former_add_end< Definition > + { + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + } + + impl< #struct_generics_impl Types2, Definition > former::FormingEnd< Types2, > + for #former_add_end< Definition > + where + Definition : former::FormerDefinition + < + Storage = < #stru < #struct_generics_ty > as former::EntityToStorage >::Storage, + >, + Types2 : former::FormerDefinitionTypes + < + Storage = < < #field_typ as former::Container >::Val as former::EntityToStorage >::Storage, + Formed = #former< #former_generics_ty >, + Context = #former< #former_generics_ty >, + >, + #struct_generics_where + { + #[ inline( always ) ] + fn call + ( + &self, + substorage : Types2::Storage, + super_former : core::option::Option< Types2::Context >, + ) + -> Types2::Formed + { + let mut super_former = super_former.unwrap(); + if super_former.storage.#field_ident.is_none() + { + super_former.storage.#field_ident = Some( Default::default() ); + } + if let Some( ref mut field ) = super_former.storage.#field_ident + { + former::ContainerAdd::add + ( + field, + < < #field_typ as former::Container >::Val as former::ValToEntry< #field_typ > > + ::val_to_entry( former::StoragePreform::preform( substorage ) ), + ); + } + super_former + } + } + + }; + + // tree_print!( setters_code.as_ref().unwrap() ); + Ok( ( setters_code, namespace_code ) ) + } + + /// + /// Generate a container setter for the 'field_ident' with the 'setter_name' name. + /// + /// See `examples/subformer_container_manual.rs` for example of generated code. + + #[ inline ] + pub fn container_setter + ( + &self, + stru : &syn::Ident, + former : &syn::Ident, + former_storage : &syn::Ident, + former_generics_impl : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + former_generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + former_generics_where : &syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, + original_input : &proc_macro::TokenStream, + ) + -> Result< ( TokenStream, TokenStream ) > + { + let attr = self.attrs.container.as_ref().unwrap(); + let field_ident = &self.ident; + let field_typ = &self.non_optional_ty; + let params = typ::type_parameters( &field_typ, .. ); + + use convert_case::{ Case, Casing }; + let former_assign_end_name = format!( "{}FormerAssign{}End", stru, field_ident.to_string().to_case( Case::Pascal ) ); + let former_assign_end = syn::Ident::new( &former_assign_end_name, field_ident.span() ); + let field_assign_name = format!( "_{}_container_former", field_ident ); + let field_assign = syn::Ident::new( &field_assign_name, field_ident.span() ); + + // example : `former::VectorDefinition` + let subformer_definition = &attr.definition; + let subformer_definition = if subformer_definition.is_some() + { + qt! + { + #subformer_definition + < + #( #params, )* + Self, + Self, + #former_assign_end< Definition >, + > + } + // former::VectorDefinition< String, Self, Self, Struct1FormerAssignVec1End, > + } + else + { + qt! + { + < + #field_typ as former::EntityToDefinition< Self, Self, #former_assign_end< Definition > > + >::Definition + } + // < Vec< String > as former::EntityToDefinition< Self, Self, Struct1FormerAssignVec1End > >::Definition + }; + + let doc = format! + ( + "Container setter for the '{}' field. Method {} unlike method {} accept custom container subformer.", + field_ident, + field_assign_name, + field_ident, + ); + + let setter1 = + qt! + { + + #[ doc = #doc ] + #[ inline( always ) ] + pub fn #field_assign< Former2 >( self ) -> Former2 + where + Former2 : former::FormerBegin + < + #subformer_definition, + >, + #subformer_definition : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < #field_typ as former::Container >::Entry >, + Storage = #field_typ, + Context = #former< #former_generics_ty >, + End = #former_assign_end< Definition >, + >, + { + Former2::former_begin( None, Some( self ), #former_assign_end::< Definition >::default() ) + } + + // #[ inline( always ) ] + // pub fn _hashset_1_assign< Former2 >( self ) -> Former2 + // where + // Former2 : former::FormerBegin + // < + // former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > >, + // >, + // former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > > : former::FormerDefinition + // < + // Storage : former::ContainerAdd< Entry = < collection_tools::HashSet< String > as former::Container >::Entry >, + // Context = Struct1Former< Definition >, + // End = Struct1FormerAssignHashset1End< Definition >, + // >, + // { + // Former2::former_begin( None, Some( self ), Struct1FormerAssignHashset1End::< Definition >::default() ) + // } + + }; + + let setter_name = self.container_setter_name(); + let setter2 = if let Some( setter_name ) = setter_name + { + qt! + { + + #[ doc = #doc ] + #[ inline( always ) ] + pub fn #setter_name( self ) -> former::ContainerFormer:: + < + // ( #( #params, )* ), + < #field_typ as former::Container >::Entry, + #subformer_definition, + > + where + #subformer_definition : former::FormerDefinition + < + // Storage : former::ContainerAdd< Entry = < #field_typ as former::Container >::Entry >, + Storage = #field_typ, + Context = #former< #former_generics_ty >, + End = #former_assign_end < Definition >, + >, + { + self.#field_assign::< former::ContainerFormer:: + < + _, + _, + // ( #( #params, )* ), + // #subformer_definition, + > > () + } + + // #[ inline( always ) ] + // pub fn hashset_1( self ) -> former::ContainerFormer:: + // < + // String, + // former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > >, + // > + // where + // former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > > : former::FormerDefinition + // < + // Storage : former::ContainerAdd< Entry = < collection_tools::HashSet< String > as former::Container >::Entry >, + // Context = Struct1Former< Definition >, + // End = Struct1FormerAssignHashset1End< Definition >, + // >, + // { + // self._hashset_1_assign::< former::ContainerFormer:: + // < + // String, + // former::HashSetDefinition< String, Self, Self, Struct1FormerAssignHashset1End< Definition > >, + // > > () + // } + + } + } + else + { + qt!{} + }; + + if attr.hint + { + let hint = format! + ( + r#" + +/// The containr setter provides a container setter that returns a ContainerFormer tailored for managing a collection of child entities. It employs a generic container definition to facilitate operations on the entire collection, such as adding or updating elements. + +impl< Definition, > {}< Definition, > +where + Definition : former::FormerDefinition< Storage = {} >, +{{ + + #[ inline( always ) ] + pub fn {}( self ) -> former::ContainerFormer:: + < + ( {} ), + former::HashMapDefinition< {} Self, Self, {}< Definition >, > + // Replace `HashMapDefinition` with definition for your container + > + {{ + self.{}() + }} + +}} + + "#, + former, + former_storage, + field_ident, + format!( "{}", qt!{ #( #params, )* } ), + format!( "{}", qt!{ #( #params, )* } ), + former_assign_end, + field_assign, + ); + let about = format! + ( +r#"derive : Former +structure : {stru} +field : {field_ident}"#, + ); + diag::report_print( about, original_input, hint ); + } + + let setters_code = qt! + { + #setter1 + #setter2 + }; + + // example : `former::VectorDefinition`` + let subformer_definition = &self.attrs.container.as_ref().unwrap().definition; + + let former_assign_end_doc = format! + ( + r#" +A callback structure to manage the final stage of forming a `{0}` for the `{stru}` container. + +This callback is used to integrate the contents of a temporary `{0}` back into the original `{stru}` former +after the subforming process is completed. It replaces the existing content of the `{field_ident}` field in `{stru}` +with the new content generated during the subforming process. + "#, + format!( "{}", qt!{ #field_typ } ), + ); + + let subformer_definition_types = if let Some( ref _subformer_definition ) = subformer_definition + { + let subformer_definition_types_string = format!( "{}Types", qt!{ #subformer_definition } ); + let subformer_definition_types : syn::Type = syn::parse_str( &subformer_definition_types_string )?; + qt! + { + #subformer_definition_types + < + #( #params, )* + #former< #former_generics_ty >, + #former< #former_generics_ty >, + > + } + } + else + { + qt! + { + < + #field_typ as former::EntityToDefinitionTypes + < + #former< #former_generics_ty >, + #former< #former_generics_ty >, + > + >::Types + } + }; + + let r = qt! + { + + #[ doc = #former_assign_end_doc ] + pub struct #former_assign_end< Definition > + { + _phantom : core::marker::PhantomData< ( Definition, ) >, + } + + impl< Definition > Default + for #former_assign_end< Definition > + { + + #[ inline( always ) ] + fn default() -> Self + { + Self + { + _phantom : core::marker::PhantomData, + } + } + + } + + #[ automatically_derived ] + impl< #former_generics_impl > former::FormingEnd + < + // VectorDefinitionTypes + #subformer_definition_types, + > + for #former_assign_end< Definition > + where + #former_generics_where + { + #[ inline( always ) ] + fn call + ( + &self, + storage : #field_typ, + super_former : Option< #former< #former_generics_ty > >, + ) + -> #former< #former_generics_ty > + { + let mut super_former = super_former.unwrap(); + if let Some( ref mut field ) = super_former.storage.#field_ident + { + former::ContainerAssign::assign( field, storage ); + } + else + { + super_former.storage.#field_ident = Some( storage ); + } + super_former + } + } + + }; + + // tree_print!( r.as_ref().unwrap() ); + let namespace_code = r; + + Ok( ( setters_code, namespace_code ) ) + } + + /// + /// Generate a single scalar setter for the 'field_ident' with the 'setter_name' name. + /// + /// Used as a helper function for former_field_setter(), which generates alias setters + /// + /// # Example of generated code + /// + /// ```ignore + /// #[ doc = "Setter for the 'int_1' field." ] + /// #[ inline ] + /// pub fn int_1< Src >( mut self, src : Src ) -> Self + /// where + /// Src : ::core::convert::Into< i32 >, + /// { + /// debug_assert!( self.int_1.is_none() ); + /// self.storage.int_1 = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + /// self + /// } + /// ``` + + #[ inline ] + pub fn scalar_setter + ( + &self, + former : &syn::Ident, + former_storage : &syn::Ident, + ) + -> TokenStream + { + let field_ident = self.ident; + let typ = self.non_optional_ty; + let setter_name = self.scalar_setter_name(); + let attr = self.attrs.scalar.as_ref(); + + if attr.is_some() && attr.unwrap().hint + { + let hint = format! + ( + r#" + +impl< Definition > {}< Definition > +where + Definition : former::FormerDefinition< Storage = {} >, +{{ + #[ inline ] + pub fn {}< Src >( mut self, src : Src ) -> Self + where + Src : ::core::convert::Into< {} >, + {{ + debug_assert!( self.storage.{}.is_none() ); + self.storage.{} = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + self + }} +}} + + "#, + former, + former_storage, + field_ident, + format!( "{}", qt!{ #typ } ), + field_ident, + field_ident, + ); + println!( "{hint}" ); + } + + if !self.scalar_setter_required() + { + return qt! {}; + } + + let doc = format! + ( + "Scalar setter for the '{}' field.", + field_ident, + ); + + qt! + { + #[ doc = #doc ] + #[ inline ] + pub fn #setter_name< Src >( mut self, src : Src ) -> Self + where + Src : ::core::convert::Into< #typ >, + { + debug_assert!( self.storage.#field_ident.is_none() ); + self.storage.#field_ident = ::core::option::Option::Some( ::core::convert::Into::into( src ) ); + self + } + } + } + + /// Get name of scalar setter. + pub fn scalar_setter_name( &self ) -> &syn::Ident + { + if let Some( ref attr ) = self.attrs.scalar + { + if let Some( ref name ) = attr.name + { + return name + } + } + return &self.ident; + } + + /// Get name of setter for container if such setter should be generated. + pub fn container_setter_name( &self ) -> Option< &syn::Ident > + { + + if let Some( ref attr ) = self.attrs.container + { + if attr.setter() + { + if let Some( ref name ) = attr.name + { + return Some( &name ) + } + else + { + return Some( &self.ident ) + } + } + } + + return None; + } + + /// Get name of setter for subform if such setter should be generated. + pub fn subform_setter_name( &self ) -> Option< &syn::Ident > + { + + if let Some( ref attr ) = self.attrs.subform + { + if attr.setter() + { + if let Some( ref name ) = attr.name + { + return Some( &name ) + } + else + { + return Some( &self.ident ) + } + } + } + + return None; + } + + /// Is scalar setter required. Does not if container of subformer setter requested. + pub fn scalar_setter_required( &self ) -> bool + { + + let mut explicit = false; + if let Some( ref attr ) = self.attrs.scalar + { + if let Some( setter ) = attr.setter + { + if setter == false + { + return false + } + explicit = true; + } + if let Some( ref _name ) = attr.name + { + explicit = true; + } + } + + if self.attrs.container.is_some() && !explicit + { + return false; + } + + if self.attrs.subform.is_some() && !explicit + { + return false; + } + + return true; + } + +} diff --git a/module/core/former_meta/src/derive_former/field_attrs.rs b/module/core/former_meta/src/derive_former/field_attrs.rs new file mode 100644 index 0000000000..5aea94b80d --- /dev/null +++ b/module/core/former_meta/src/derive_former/field_attrs.rs @@ -0,0 +1,442 @@ + +use super::*; +use macro_tools::{ attr, Result }; + +/// +/// Attributes of a field. +/// + +pub struct FieldAttributes +{ + pub config : Option< AttributeConfig >, + pub scalar : Option< AttributeScalarSetter >, + pub container : Option< AttributeContainerSetter >, + pub subform : Option< AttributeSubformSetter >, +} + +impl FieldAttributes +{ + // fn from_attrs( attributes : & Vec< syn::Attribute > ) -> Result< Self > + pub fn from_attrs< 'a >( attrs : impl Iterator< Item = &'a syn::Attribute > ) -> Result< Self > + { + let mut config = None; + let mut scalar = None; + let mut container = None; + let mut subform = None; + for attr in attrs + { + let key_ident = attr.path().get_ident() + .ok_or_else( || syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ) )?; + let key_str = format!( "{}", key_ident ); + + if attr::is_standard( &key_str ) + { + continue; + } + + match key_str.as_ref() + { + "former" => + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + config.replace( syn::parse2::< AttributeConfig >( meta_list.tokens.clone() )? ); + }, + syn::Meta::Path( ref _path ) => + { + config.replace( syn::parse2::< AttributeConfig >( Default::default() )? ); + }, + _ => return_syn_err!( attr, "Expects an attribute of format #[ former( default = 13 ) ].\nGot: {}", qt!{ #attr } ), + } + } + "scalar" => + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + scalar.replace( syn::parse2::< AttributeScalarSetter >( meta_list.tokens.clone() )? ); + }, + syn::Meta::Path( ref _path ) => + { + scalar.replace( syn::parse2::< AttributeScalarSetter >( Default::default() )? ); + }, + _ => return_syn_err!( attr, "Expects an attribute of format `#[ scalar( setter = false ) ]` or `#[ scalar( setter = false, name = my_name ) ]`. \nGot: {}", qt!{ #attr } ), + } + } + "container" => + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + container.replace( syn::parse2::< AttributeContainerSetter >( meta_list.tokens.clone() )? ); + }, + syn::Meta::Path( ref _path ) => + { + container.replace( syn::parse2::< AttributeContainerSetter >( Default::default() )? ); + }, + _ => return_syn_err!( attr, "Expects an attribute of format `#[ container ]` or `#[ container( definition = former::VectorDefinition ) ]` if you want to use default container defition. \nGot: {}", qt!{ #attr } ), + } + } + "subform" => + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + subform.replace( syn::parse2::< AttributeSubformSetter >( meta_list.tokens.clone() )? ); + }, + syn::Meta::Path( ref _path ) => + { + subform.replace( syn::parse2::< AttributeSubformSetter >( Default::default() )? ); + }, + _ => return_syn_err!( attr, "Expects an attribute of format `#[ subform ]` or `#[ subform( name : child )` ], \nGot: {}", qt!{ #attr } ), + } + } + _ => + { + return Err( syn_err!( attr, "Unknown field attribute {}", qt!{ #attr } ) ); + } + } + } + + Ok( FieldAttributes { config, scalar, container, subform } ) + } +} + +/// +/// Attribute to hold configuration information about the field such as default value. +/// +/// `#[ default( 13 ) ]` +/// + +pub struct AttributeConfig +{ + + /// Default value to use for the field. + pub default : Option< syn::Expr >, + +} + +impl AttributeConfig +{ +} + +impl syn::parse::Parse for AttributeConfig +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + let mut default : Option< syn::Expr > = None; + // let mut only_storage : Option< bool > = None; + + while !input.is_empty() + { + let lookahead = input.lookahead1(); + if lookahead.peek( syn::Ident ) + { + let ident : syn::Ident = input.parse()?; + if ident == "default" + { + input.parse::< syn::Token![ = ] >()?; + default = Some( input.parse()? ); + } + else + { + return Err( syn::Error::new_spanned( &ident, format!( "Unexpected identifier '{}'. Expected 'default'. For example: `former( default = 13 )`", ident ) ) ); + } + } + + else + { + return Err( syn::Error::new( input.span(), "Expected 'default'. For example: `former( default = 13 )`" ) ); + } + + // Optional comma handling + if input.peek( syn::Token![ , ] ) + { + input.parse::< syn::Token![ , ] >()?; + } + } + + Ok( Self { default } ) + } +} + +/// +/// Attribute to enable/disable scalar setter generation. +/// +/// ## Example Input +/// +/// A typical input to parse might look like the following: +/// +/// ```ignore +/// name = field_name, setter = true +/// ``` +/// + +pub struct AttributeScalarSetter +{ + /// Optional identifier for naming the setter. + pub name : Option< syn::Ident >, + /// Controls the generation of a setter method. If false, a setter method is not generated. + pub setter : Option< bool >, + /// Specifies whether to provide a sketch of the subform setter as a hint. + /// Defaults to `false`, which means no hint is provided unless explicitly requested. + pub hint : bool, +} + +#[ allow( dead_code ) ] +impl AttributeScalarSetter +{ + + /// Should setter be generated or not? + pub fn setter( &self ) -> bool + { + self.setter.is_none() || self.setter.unwrap() + } + +} + +impl syn::parse::Parse for AttributeScalarSetter +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + let mut name : Option< syn::Ident > = None; + let mut setter : Option< bool > = None; + let mut hint = false; + + while !input.is_empty() + { + let lookahead = input.lookahead1(); + if lookahead.peek( syn::Ident ) + { + let ident : syn::Ident = input.parse()?; + if ident == "name" + { + input.parse::< syn::Token![ = ] >()?; + name = Some( input.parse()? ); + } + else if ident == "setter" + { + input.parse::< syn::Token![ = ] >()?; + let value : syn::LitBool = input.parse()?; + setter = Some( value.value() ); + } + else if ident == "hint" + { + input.parse::< syn::Token![ = ] >()?; + let value : syn::LitBool = input.parse()?; + hint = value.value; + } + else + { + return Err( syn::Error::new_spanned( &ident, format!( "Unexpected identifier '{}'. Expected 'name', 'setter', or 'definition'. For example: `scalar( name = myName, setter = true )`", ident ) ) ); + } + } + else + { + return Err( syn::Error::new( input.span(), "Expected 'name', 'setter', or 'definition' identifier. For example: `scalar( name = myName, setter = true )`" ) ); + } + + // Optional comma handling + if input.peek( syn::Token![,] ) + { + input.parse::< syn::Token![,] >()?; + } + } + + Ok( Self { name, setter, hint } ) + } +} + +/// Represents an attribute for configuring container setter generation. +/// +/// This struct is part of a meta-programming approach to enable detailed configuration of nested structs or collections such as `Vec< E >, HashMap< K, E >` and so on. +/// It allows the customization of setter methods and the specification of the container's behavior through meta attributes. +/// +/// ## Example Input +/// +/// The following is an example of a token stream that this struct can parse: +/// ```ignore +/// name = "custom_setter", setter = true, definition = former::VectorDefinition +/// ``` +/// + +pub struct AttributeContainerSetter +{ + /// Optional identifier for naming the setter. + pub name : Option< syn::Ident >, + /// Controls the generation of a setter method. If false, a setter method is not generated. + pub setter : Option< bool >, + /// Definition of the container former to use, e.g., `former::VectorFormer`. + pub definition : Option< syn::Type >, + /// Specifies whether to provide a sketch of the subform setter as a hint. + /// Defaults to `false`, which means no hint is provided unless explicitly requested. + pub hint : bool, +} + +impl AttributeContainerSetter +{ + + /// Should setter be generated or not? + pub fn setter( &self ) -> bool + { + self.setter.is_none() || self.setter.unwrap() + } + +} + +impl syn::parse::Parse for AttributeContainerSetter +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + let mut name : Option< syn::Ident > = None; + let mut setter : Option< bool > = None; // Default is to generate a setter + let mut hint = false; + let mut definition : Option< syn::Type > = None; + + while !input.is_empty() + { + let lookahead = input.lookahead1(); + if lookahead.peek( syn::Ident ) + { + let ident : syn::Ident = input.parse()?; + if ident == "name" + { + input.parse::< syn::Token![ = ] >()?; + name = Some( input.parse()? ); + } + else if ident == "setter" + { + input.parse::< syn::Token![ = ] >()?; + let value : syn::LitBool = input.parse()?; + setter = Some( value.value ); + } + else if ident == "hint" + { + input.parse::< syn::Token![ = ] >()?; + let value : syn::LitBool = input.parse()?; + hint = value.value; + } + else if ident == "definition" + { + input.parse::< syn::Token![ = ] >()?; + definition = Some( input.parse()? ); + } + else + { + return Err( syn::Error::new_spanned( &ident, format!( "Unexpected identifier '{}'. Expected 'name', 'setter', or 'definition'. For example: `container( name = myName, setter = true, definition = MyDefinition )`", ident ) ) ); + } + } + else + { + return Err( syn::Error::new( input.span(), "Expected 'name', 'setter', or 'definition' identifier. For example: `container( name = myName, setter = true, definition = MyDefinition )`" ) ); + } + + // Optional comma handling + if input.peek( syn::Token![ , ] ) + { + input.parse::< syn::Token![ , ] >()?; + } + } + + Ok( Self { name, setter, hint, definition } ) + } +} + +/// Represents a subform attribute to control subform setter generation. +/// Used to specify extra options for using one former as subformer of another one. +/// For example name of setter could be customized. +/// +/// ## Example Input +/// +/// A typical input to parse might look like the following: +/// +/// ```ignore +/// name = field_name, setter = true +/// ``` +/// +/// or simply: +/// +/// ```ignore +/// mame = field_name +/// ``` + +pub struct AttributeSubformSetter +{ + /// An optional identifier that names the setter. It is parsed from inputs + /// like `name = my_field`. + pub name : Option< syn::Ident >, + /// Disable generation of setter. + /// It still generate `_field_add` method, so it could be used to make a setter with custom arguments. + pub setter : Option< bool >, + /// Specifies whether to provide a sketch of the subform setter as a hint. + /// Defaults to `false`, which means no hint is provided unless explicitly requested. + pub hint : bool, +} + +impl AttributeSubformSetter +{ + + /// Should setter be generated or not? + pub fn setter( &self ) -> bool + { + self.setter.is_none() || self.setter.unwrap() + } + +} + +impl syn::parse::Parse for AttributeSubformSetter +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + let mut name : Option< syn::Ident > = None; + let mut setter : Option< bool > = None; + let mut hint = false; + + while !input.is_empty() + { + let lookahead = input.lookahead1(); + if lookahead.peek( syn::Ident ) + { + let ident : syn::Ident = input.parse()?; + if ident == "name" + { + input.parse::< syn::Token![ = ] >()?; + name = Some( input.parse()? ); + } + else if ident == "setter" + { + input.parse::< syn::Token![ = ] >()?; + let value : syn::LitBool = input.parse()?; + setter = Some( value.value() ); + } + else if ident == "hint" + { + input.parse::< syn::Token![ = ] >()?; + let value : syn::LitBool = input.parse()?; + hint = value.value; + } + else + { + return Err( syn::Error::new_spanned( &ident, format!( "Unexpected identifier '{}'. Expected 'name', 'setter', or 'definition'. For example: `subform( name = myName, setter = true )`", ident ) ) ); + } + } + else + { + return Err( syn::Error::new( input.span(), "Expected 'name', 'setter', or 'definition' identifier. For example: `subform( name = myName, setter = true )`" ) ); + } + + // Optional comma handling + if input.peek( syn::Token![,] ) + { + input.parse::< syn::Token![,] >()?; + } + } + + Ok( Self { name, setter, hint } ) + } +} diff --git a/module/core/former_meta/src/derive_former/struct_attrs.rs b/module/core/former_meta/src/derive_former/struct_attrs.rs new file mode 100644 index 0000000000..c2a15bb9cf --- /dev/null +++ b/module/core/former_meta/src/derive_former/struct_attrs.rs @@ -0,0 +1,299 @@ + +use super::*; +use macro_tools::{ attr, Result }; + +/// +/// Definition of a field. +/// + +/// +/// Attributes of a struct. +/// + +pub struct StructAttributes +{ + pub perform : Option< AttributePerform >, + pub storage_fields : Option< AttributeStorageFields >, + pub mutator : AttributeMutator, +} + +impl StructAttributes +{ + + pub fn from_attrs< 'a >( attrs : impl Iterator< Item = &'a syn::Attribute > ) -> Result< Self > + { + let mut perform = None; + let mut storage_fields = None; + let mut mutator = Default::default(); + + for attr in attrs + { + let key_ident = attr.path().get_ident() + .ok_or_else( || syn_err!( attr, "Expects an attribute of format #[ attribute( val ) ], but got:\n {}", qt!{ #attr } ) )?; + let key_str = format!( "{}", key_ident ); + + if attr::is_standard( &key_str ) + { + continue; + } + + match key_str.as_ref() + { + "storage_fields" => + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + storage_fields.replace( syn::parse2::< AttributeStorageFields >( meta_list.tokens.clone() )? ); + }, + _ => return_syn_err!( attr, "Expects an attribute of format #[ storage_fields( a : i32, b : Option< String > ) ] +.\nGot: {}", qt!{ #attr } ), + } + } + "perform" => + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + perform.replace( syn::parse2::< AttributePerform >( meta_list.tokens.clone() )? ); + }, + _ => return_syn_err!( attr, "Expects an attribute of format #[ perform( fn parse( mut self ) -> Request ) ] +.\nGot: {}", qt!{ #attr } ), + } + } + "mutator" => + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + mutator = syn::parse2::< AttributeMutator >( meta_list.tokens.clone() )? + }, + syn::Meta::Path( ref _path ) => + { + }, + _ => return_syn_err!( attr, "Expects an attribute of format `#[ mutator( custom = true, hint = true ) ]`. \nGot: {}", qt!{ #attr } ), + } + } + "debug" => + { + } + _ => + { + return Err( syn_err!( attr, "Known structure attirbutes are : `storage_fields`, `perform`, `debug`.\nUnknown structure attribute : {}", qt!{ #attr } ) ); + } + } + } + + Ok( StructAttributes { perform, storage_fields, mutator } ) + } + + + /// + /// Generate parts, used for generating `perform()`` method. + /// + /// Similar to `form()`, but will also invoke function from `perform` attribute, if specified. + /// + /// # Example of returned tokens : + /// + /// ## perform : + /// return result; + /// + /// ## perform_output : + /// < T : ::core::default::Default > + /// + /// ## perform_generics : + /// Vec< T > + /// + + pub fn performer( &self ) + -> Result< ( TokenStream, TokenStream, TokenStream ) > + { + + let mut perform = qt! + { + return result; + }; + let mut perform_output = qt!{ Definition::Formed }; + let mut perform_generics = qt!{}; + + if let Some( ref attr ) = self.perform + { + + // let attr_perform = syn::parse2::< AttributePerform >( meta_list.tokens.clone() )?; + let signature = &attr.signature; + let generics = &signature.generics; + perform_generics = qt!{ #generics }; + let perform_ident = &signature.ident; + let output = &signature.output; + if let syn::ReturnType::Type( _, boxed_type ) = output + { + perform_output = qt!{ #boxed_type }; + } + perform = qt! + { + return result.#perform_ident(); + }; + + } + + Ok( ( perform, perform_output, perform_generics ) ) + } + + /// Returns an iterator over the fields defined in the `storage_fields` attribute. + /// + /// This function provides an iterator that yields `syn::Field` objects. If `storage_fields` is set, + /// it clones and iterates over its fields. If `storage_fields` is `None`, it returns an empty iterator. + /// + + // pub fn storage_fields( &self ) -> impl Iterator< Item = syn::Field > + pub fn storage_fields( &self ) -> &syn::punctuated::Punctuated< syn::Field, syn::token::Comma > + { + + self.storage_fields.as_ref().map_or_else + ( + || &*Box::leak( Box::new( syn::punctuated::Punctuated::new() ) ), + | attr | &attr.fields + ) + // qqq : find better solutioin + + // self.storage_fields + // .as_ref() + // .map_or_else( + // || syn::punctuated::Punctuated::< syn::Field, syn::token::Comma >::new().into_iter(), + // | attr | attr.fields.clone().into_iter() + // // Clone and create an iterator when storage_fields is Some + // ) + } + +} + +/// +/// Attribute to hold information about method to call after form. +/// +/// `#[ perform( fn after1< 'a >() -> Option< &'a str > ) ]` +/// + +pub struct AttributePerform +{ + pub signature : syn::Signature, +} + +impl syn::parse::Parse for AttributePerform +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> Result< Self > + { + // let input2; + Ok( Self + { + // paren_token : syn::parenthesized!( input2 in input ), + // signature : input2.parse()?, + signature : input.parse()?, + }) + } +} + +/// +/// Attribute to hold storage-specific fields. +/// Useful if formed structure should not have such fields. +/// +/// `#[ storage_fields( a : i32, b : Option< String > ) ]` +/// + +pub struct AttributeStorageFields +{ + pub fields : syn::punctuated::Punctuated< syn::Field, syn::token::Comma >, +} + +impl syn::parse::Parse for AttributeStorageFields +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + + let fields : syn::punctuated::Punctuated< syn::Field, syn::Token![,] > = + input.parse_terminated( syn::Field::parse_named, Token![,] )?; + + Ok( Self + { + fields, + // fields : syn::Fields::Named( syn::FieldsNamed + // { + // brace_token : Default::default(), + // named : fields, + // }), + }) + } +} + +/// Represents attributes for customizing the mutation process in a forming operation. +/// +/// `AttributeMutator` allows specifying whether a custom mutator should be used or a sketch should be provided +/// as a hint for developing a custom mutator. This is crucial for advanced scenarios where the entity's state +/// might require conditional modifications which are not handled by the standard `FormingEnd`. +/// +/// ## Example of code +/// ```ignore +/// custom = true, hint = true +/// ``` + +#[ derive( Debug, Default ) ] +pub struct AttributeMutator +{ + /// Indicates whether a custom mutator should be generated. + /// Defaults to `false`, meaning no custom mutator is generated unless explicitly requested. + pub custom : bool, + /// Specifies whether to provide a sketch of the mutator as a hint. + /// Defaults to `false`, which means no hint is provided unless explicitly requested. + pub hint : bool, +} + +impl syn::parse::Parse for AttributeMutator +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + let mut custom = false; + let mut hint = false; + + while !input.is_empty() + { + let lookahead = input.lookahead1(); + if lookahead.peek( syn::Ident ) + { + let ident : syn::Ident = input.parse()?; + input.parse::< syn::Token![=] >()?; + if ident == "custom" + { + let value : syn::LitBool = input.parse()?; + custom = value.value; + } + else if ident == "hint" + { + let value : syn::LitBool = input.parse()?; + hint = value.value; + } + else + { + return Err( syn::Error::new_spanned( &ident, format!( "Unexpected identifier '{}'. Expected 'custom' or 'hint'.", ident ) ) ); + } + } + else + { + return Err( lookahead.error() ); + } + + // Optional comma handling + if input.peek( syn::Token![,] ) + { + input.parse::< syn::Token![,] >()?; + } + } + + Ok( Self + { + custom, + hint, + }) + } +} diff --git a/module/core/former_meta/src/lib.rs b/module/core/former_meta/src/lib.rs index e02c582eaf..cf131eef6f 100644 --- a/module/core/former_meta/src/lib.rs +++ b/module/core/former_meta/src/lib.rs @@ -4,7 +4,8 @@ #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] #[ cfg( feature = "enabled" ) ] -mod derive +// #[ cfg( feature = "derive_component_from" ) ] +mod component { //! @@ -14,8 +15,6 @@ mod derive #[ allow( unused_imports ) ] use macro_tools::prelude::*; - #[ cfg( feature = "derive_former" ) ] - pub mod former; #[ cfg( feature = "derive_component_from" ) ] pub mod component_from; #[ cfg( feature = "derive_from_components" ) ] @@ -27,79 +26,46 @@ mod derive } -/// -/// Derive macro to generate former for a structure. Former is variation of Builder Pattern. -/// +#[ allow( unused_imports ) ] +use macro_tools::prelude::*; +#[ cfg( feature = "derive_former" ) ] +mod derive_former; -/// Derives a 'Former' for a struct, implementing a variation of the Builder Pattern. -/// -/// This macro simplifies the creation of builder patterns for structs by automatically -/// generating a 'former' (builder) struct and implementation. It supports customization -/// through attributes to control default values, setter generation, subformer inclusion, -/// and field aliases. +/// Derive macro for generating a `Former` struct, applying a Builder Pattern to the annotated struct. /// -/// # Attributes : -/// - `perform` : Specifies a method to call on the built object immediately after its construction. -/// - `default` : Sets a default value for a field. -/// - `setter` : Enables or disables the generation of a setter method for a field. -/// - `subformer` : Defines a sub-former for complex field types, allowing nested builders. -/// - `alias` : Creates an alias for a field setter. -/// - `doc` : Adds documentation to the generated setter methods. (deprecated) +/// This macro simplifies the construction of complex objects by automatically generating a builder (former) for +/// the specified struct. It supports extensive customization through attributes that control defaults, setter generation, +/// and field customization, allowing for flexible and fluent object construction. /// -/// # Input Example : -/// -/// ```rust -/// use former::Former; +/// # Struct Attributes /// -/// #[ derive( Debug, PartialEq, Former ) ] -/// #[ perform( fn greet_user() ) ] -/// pub struct UserProfile -/// { -/// #[default(1)] -/// age : i32, +/// - `debug`: Enables debug mode which can be used to print or log the internal state of the builder for debugging purposes. +/// - `perform`: Specifies a custom method to be invoked automatically at the end of the build process. +/// - `storage_fields`: Specifies fields that should be treated as part of the storage for the former. +/// - `mutator`: Defines a custom mutator class or function to manipulate the data just before the object is finalized. /// -/// username : String, +/// # Field Attributes /// -/// #[alias(bio)] -/// bio_optional : Option< String >, // Fields could be optional -/// } -/// -/// impl UserProfile -/// { -/// fn greet_user(self) -> Self -/// { -/// println!("Hello, {}", self.username); -/// self -/// } -/// } -/// -/// let profile = UserProfile::former() -/// .age( 30 ) -/// .username( "JohnDoe".to_string() ) -/// .bio_optional( "Software Developer".to_string() ) // Optionally provide a bio -/// .form(); -/// // .perform(); // same as `form()` but will execute method passed to perform attribute -/// -/// dbg!( &profile ); -/// // Expected output: -/// // &profile = UserProfile { -/// // age: 30, -/// // username: "JohnDoe", -/// // bio_optional: Some("Software Developer"), -/// // } -/// ``` +/// - `former`: General attribute to specify various options like defaults or inclusion in the former. +/// - `scalar`: Indicates that the field is a scalar value, enabling direct assignment without the need for a sub-former. +/// - `container`: Marks the field as a container that can use specific former methods to manage its contents. +/// - `subform`: Specifies that the field should utilize a nested former, facilitating the construction of complex nested structures. /// -/// # Generated Code Example : +/// # Usage Example /// -/// Assuming the struct above, the macro generates something like this : +/// Below is a typical usage example where the macro is applied to a struct: /// /// ```rust -/// # #[ cfg( feature = "enabled" ) ] -/// # #[ allow( dead_code ) ] +/// +/// # #[ cfg( all( feature = "derive_former", feature = "enabled" ) ) ] /// # fn main() /// # { +/// use former::Former; /// -/// #[ derive( Debug, PartialEq ) ] +/// // Use attribute debug to print expanded code. +/// #[ derive( Debug, PartialEq, Former ) ] +/// // Uncomment to see what derive expand into +/// // #[ debug ] /// pub struct UserProfile /// { /// age : i32, @@ -107,154 +73,10 @@ mod derive /// bio_optional : Option< String >, // Fields could be optional /// } /// -/// impl UserProfile -/// { -/// fn greet_user(self) -> Self -/// { -/// println!("Hello, {}", self.username); -/// self -/// } -/// } -/// -/// impl UserProfile -/// { -/// #[ inline( always ) ] -/// pub fn former() -> UserProfileFormer< UserProfile, former::ReturnFormed > -/// { -/// UserProfileFormer::< UserProfile, former::ReturnFormed >::new() -/// } -/// } -/// -/// #[ derive( Debug, Default ) ] -/// pub struct UserProfileFormerStorage -/// { -/// age : Option< i32 >, -/// username : Option< String >, -/// bio_optional : Option< String >, -/// } -/// -/// pub struct UserProfileFormer -/// < -/// Context = UserProfile, -/// End = former::ReturnFormed, -/// > -/// where -/// End : former::FormingEnd< UserProfile, Context >, -/// { -/// storage : UserProfileFormerStorage, -/// context : Option< Context >, -/// on_end : Option< End >, -/// } -/// -/// impl< Context, End > UserProfileFormer< Context, End > -/// where -/// End : former::FormingEnd< UserProfile, Context >, -/// { -/// #[ inline( always ) ] -/// pub fn form( mut self ) -> UserProfile -/// { -/// let age = if self.storage.age.is_some() -/// { -/// self.storage.age.take().unwrap() -/// } -/// else -/// { -/// (1).into() -/// }; -/// let username = if self.storage.username.is_some() -/// { -/// self.storage.username.take().unwrap() -/// } -/// else -/// { -/// String::default() -/// }; -/// let bio_optional = if self.storage.bio_optional.is_some() -/// { -/// Some( self.storage.bio_optional.take().unwrap() ) -/// } -/// else -/// { -/// None -/// }; -/// UserProfile { age, username, bio_optional } -/// } -/// -/// #[ inline( always ) ] -/// pub fn perform( self ) -> UserProfile -/// { -/// let result = self.form(); -/// return result.greet_user(); -/// } -/// -/// // qqq : xxx : outdated, update -/// #[ inline( always ) ] -/// pub fn new() -> UserProfileFormer< UserProfile, former::ReturnFormed > -/// { -/// UserProfileFormer::< UserProfile, former::ReturnFormed >::begin( None, former::ReturnFormed ) -/// } -/// -/// #[ inline( always ) ] -/// pub fn begin( context : Option< Context >, on_end : End ) -> Self -/// { -/// Self -/// { -/// storage : Default::default(), -/// context, -/// on_end : Some( on_end ), -/// } -/// } -/// -/// #[ inline( always ) ] -/// pub fn end( mut self ) -> Context -/// { -/// let on_end = self.on_end.take().unwrap(); -/// let context = self.context.take(); -/// let formed = self.form(); -/// on_end.call( formed, context ) -/// } -/// -/// #[ inline ] -/// pub fn age< Src >( mut self, src : Src ) -> Self -/// where -/// Src : Into< i32 >, -/// { -/// self.storage.age = Some( src.into() ); -/// self -/// } -/// -/// #[ inline ] -/// pub fn username< Src >( mut self, src : Src ) -> Self -/// where -/// Src : Into< String >, -/// { -/// self.storage.username = Some( src.into() ); -/// self -/// } -/// -/// #[ inline ] -/// pub fn bio_optional< Src >( mut self, src : Src ) -> Self -/// where -/// Src : Into< String >, -/// { -/// self.storage.bio_optional = Some( src.into() ); -/// self -/// } -/// -/// #[inline] -/// pub fn bio< Src >( mut self, src : Src ) -> Self -/// where -/// Src : Into< String >, -/// { -/// self.storage.bio_optional = Some( src.into() ); -/// self -/// } -/// } -/// /// let profile = UserProfile::former() /// .age( 30 ) /// .username( "JohnDoe".to_string() ) -/// .bio_optional( "Software Developer".to_string() ) +/// .bio_optional( "Software Developer".to_string() ) // Optionally provide a bio /// .form(); /// /// dbg!( &profile ); @@ -264,17 +86,29 @@ mod derive /// // username: "JohnDoe", /// // bio_optional: Some("Software Developer"), /// // } +/// /// # } +/// /// ``` /// -/// This generated code allows building an instance of `MyStruct` fluently, with optional customization for each field. +/// This pattern enables fluent and customizable construction of `UserProfile` instances, allowing for easy setting and modification of its fields. #[ cfg( feature = "enabled" ) ] #[ cfg( feature = "derive_former" ) ] -#[ proc_macro_derive( Former, attributes( debug, perform, default, setter, subformer, alias, doc, embed ) ) ] +#[ + proc_macro_derive + ( + Former, + attributes + ( + debug, perform, storage_fields, mutator, // struct attributes + former, scalar, container, subform, // field attributes + ) + ) +] pub fn former( input : proc_macro::TokenStream ) -> proc_macro::TokenStream { - let result = derive::former::former( input ); + let result = derive_former::former( input ); match result { Ok( stream ) => stream.into(), @@ -327,7 +161,7 @@ pub fn former( input : proc_macro::TokenStream ) -> proc_macro::TokenStream #[ proc_macro_derive( ComponentFrom, attributes( debug ) ) ] pub fn component_from( input : proc_macro::TokenStream ) -> proc_macro::TokenStream { - let result = derive::component_from::component_from( input ); + let result = component::component_from::component_from( input ); match result { Ok( stream ) => stream.into(), @@ -418,7 +252,7 @@ pub fn component_from( input : proc_macro::TokenStream ) -> proc_macro::TokenStr #[ proc_macro_derive( ComponentAssign, attributes( debug ) ) ] pub fn component_assign( input : proc_macro::TokenStream ) -> proc_macro::TokenStream { - let result = derive::component_assign::component_assign( input ); + let result = component::component_assign::component_assign( input ); match result { Ok( stream ) => stream.into(), @@ -665,12 +499,13 @@ pub fn component_assign( input : proc_macro::TokenStream ) -> proc_macro::TokenS /// take_smaller_opts( &options2 ); /// ``` /// + #[ cfg( feature = "enabled" ) ] #[ cfg( all( feature = "derive_component_assign", feature = "derive_components_assign" ) ) ] #[ proc_macro_derive( ComponentsAssign, attributes( debug ) ) ] pub fn components_assign( input : proc_macro::TokenStream ) -> proc_macro::TokenStream { - let result = derive::components_assign::components_assign( input ); + let result = component::components_assign::components_assign( input ); match result { Ok( stream ) => stream.into(), @@ -771,7 +606,7 @@ pub fn components_assign( input : proc_macro::TokenStream ) -> proc_macro::Token #[ proc_macro_derive( FromComponents, attributes( debug ) ) ] pub fn from_components( input : proc_macro::TokenStream ) -> proc_macro::TokenStream { - let result = derive::from_components::from_components( input ); + let result = component::from_components::from_components( input ); match result { Ok( stream ) => stream.into(), diff --git a/module/core/fs_tools/Cargo.toml b/module/core/fs_tools/Cargo.toml index 2ef39097b2..c50503253a 100644 --- a/module/core/fs_tools/Cargo.toml +++ b/module/core/fs_tools/Cargo.toml @@ -20,6 +20,9 @@ keywords = [ "fundamental", "general-purpose", "testing" ] [lints] workspace = true +[lib] +path = "src/fs/lib.rs" + [package.metadata.docs.rs] features = [ "full" ] all-features = false diff --git a/module/core/fs_tools/Readme.md b/module/core/fs_tools/Readme.md index 391104d892..a443261de2 100644 --- a/module/core/fs_tools/Readme.md +++ b/module/core/fs_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: fs_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/fs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/fs_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ffs_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20fs_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_fs_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/fs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/fs_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools to manipulate files. @@ -22,8 +21,7 @@ cd wTools cd examples/test_trivial cargo run ``` -ev -``` + ### Try out from the repository diff --git a/module/core/implements/Cargo.toml b/module/core/implements/Cargo.toml index 7abdb1be18..77f0b3ac2d 100644 --- a/module/core/implements/Cargo.toml +++ b/module/core/implements/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "implements" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/implements/Readme.md b/module/core/implements/Readme.md index 7b69a44714..8fa1c95174 100644 --- a/module/core/implements/Readme.md +++ b/module/core/implements/Readme.md @@ -2,8 +2,7 @@ # Module :: implements - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml)[![docs.rs](https://img.shields.io/docsrs/implements?color=e3e8f0&logo=docs.rs)](https://docs.rs/implements)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fimplements_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20implements_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_implements_push.yml) [![docs.rs](https://img.shields.io/docsrs/implements?color=e3e8f0&logo=docs.rs)](https://docs.rs/implements) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/implements/examples/implements_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/implements/examples/implements_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Macro to answer the question: does it implement a trait? diff --git a/module/core/implements/examples/implements_trivial_sample.rs b/module/core/implements/examples/implements_trivial.rs similarity index 100% rename from module/core/implements/examples/implements_trivial_sample.rs rename to module/core/implements/examples/implements_trivial.rs diff --git a/module/core/impls_index/Cargo.toml b/module/core/impls_index/Cargo.toml index 369edeffea..ea67dfa107 100644 --- a/module/core/impls_index/Cargo.toml +++ b/module/core/impls_index/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "impls_index" -version = "0.6.0" +version = "0.7.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/impls_index/Readme.md b/module/core/impls_index/Readme.md index 20233a064b..f8a249942e 100644 --- a/module/core/impls_index/Readme.md +++ b/module/core/impls_index/Readme.md @@ -2,8 +2,7 @@ # Module :: impls_index - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml)[![docs.rs](https://img.shields.io/docsrs/impls_index?color=e3e8f0&logo=docs.rs)](https://docs.rs/impls_index)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fimpls_index_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20impls_index_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_push.yml) [![docs.rs](https://img.shields.io/docsrs/impls_index?color=e3e8f0&logo=docs.rs)](https://docs.rs/impls_index) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/impls_index/examples/impls_index_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/impls_index/examples/impls_index_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Several of macros to put each function under a named macro to index every function in a class. diff --git a/module/core/impls_index/examples/impls_index_trivial_sample/src/main.rs b/module/core/impls_index/examples/impls_index_trivial.rs similarity index 65% rename from module/core/impls_index/examples/impls_index_trivial_sample/src/main.rs rename to module/core/impls_index/examples/impls_index_trivial.rs index 100e630abb..20f1de0781 100644 --- a/module/core/impls_index/examples/impls_index_trivial_sample/src/main.rs +++ b/module/core/impls_index/examples/impls_index_trivial.rs @@ -1,4 +1,4 @@ - +//! This example demonstrates the usage of macros `impls1!` and `index!` for defining and indexing functions. fn main() { use ::impls_index::*; diff --git a/module/core/impls_index/examples/impls_index_trivial_sample/Cargo.toml b/module/core/impls_index/examples/impls_index_trivial_sample/Cargo.toml deleted file mode 100644 index a00f2d6519..0000000000 --- a/module/core/impls_index/examples/impls_index_trivial_sample/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "impls_index_trivial" -version = "0.0.0" -edition = "2021" -publish = false - -[dependencies] -impls_index = { workspace = true } diff --git a/module/core/impls_index/examples/impls_index_trivial_sample/Readme.md b/module/core/impls_index/examples/impls_index_trivial_sample/Readme.md deleted file mode 100644 index 833e814911..0000000000 --- a/module/core/impls_index/examples/impls_index_trivial_sample/Readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# Sample - -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=sample%2Frust%2Fimpls_index_trivial,SAMPLE_FILE=.%2Fsrc%2Fmain.rs/https://github.com/Wandalen/wTools) -[![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/impls_index) diff --git a/module/core/impls_index_meta/Cargo.toml b/module/core/impls_index_meta/Cargo.toml index 3f32b836c6..d3b1c21a9f 100644 --- a/module/core/impls_index_meta/Cargo.toml +++ b/module/core/impls_index_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "impls_index_meta" -version = "0.6.0" +version = "0.7.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/impls_index_meta/Readme.md b/module/core/impls_index_meta/Readme.md index b05af13f24..30f90c0634 100644 --- a/module/core/impls_index_meta/Readme.md +++ b/module/core/impls_index_meta/Readme.md @@ -2,8 +2,7 @@ # Module :: impls_index_meta - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml)[![docs.rs](https://img.shields.io/docsrs/impls_index_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/impls_index_meta)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fimpls_index_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20impls_index_meta_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_impls_index_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/impls_index_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/impls_index_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Several of macros to put each function under a named macro to index every function in a class. diff --git a/module/core/impls_index_meta/src/impls.rs b/module/core/impls_index_meta/src/impls.rs index 6e932cc168..c2f9425fed 100644 --- a/module/core/impls_index_meta/src/impls.rs +++ b/module/core/impls_index_meta/src/impls.rs @@ -71,7 +71,7 @@ impl quote::ToTokens for Items2 { ( as $Name2 : ident ) => { - impls_index::fn_rename! + ::impls_index::fn_rename! { @Name { $Name2 } @Fn diff --git a/module/core/include_md/Readme.md b/module/core/include_md/Readme.md index abb7ee05e3..eebef4e63f 100644 --- a/module/core/include_md/Readme.md +++ b/module/core/include_md/Readme.md @@ -2,8 +2,7 @@ # Module :: include_md - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml)[![docs.rs](https://img.shields.io/docsrs/include_md?color=e3e8f0&logo=docs.rs)](https://docs.rs/include_md)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Finclude_md_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20include_md_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_include_md_push.yml) [![docs.rs](https://img.shields.io/docsrs/include_md?color=e3e8f0&logo=docs.rs)](https://docs.rs/include_md) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Include markdown file or its section. diff --git a/module/core/inspect_type/Cargo.toml b/module/core/inspect_type/Cargo.toml index a3d6f88276..8c55f439ac 100644 --- a/module/core/inspect_type/Cargo.toml +++ b/module/core/inspect_type/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inspect_type" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/inspect_type/Readme.md b/module/core/inspect_type/Readme.md index fa657ca9f3..527b31d3fb 100644 --- a/module/core/inspect_type/Readme.md +++ b/module/core/inspect_type/Readme.md @@ -2,8 +2,7 @@ # Module :: inspect_type - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml)[![docs.rs](https://img.shields.io/docsrs/inspect_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/inspect_type)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Finspect_type_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20inspect_type_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_inspect_type_push.yml) [![docs.rs](https://img.shields.io/docsrs/inspect_type?color=e3e8f0&logo=docs.rs)](https://docs.rs/inspect_type) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/inspect_type/examples/inspect_type_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/inspect_type/examples/inspect_type_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Diagnostic-purpose tools to inspect type of a variable and its size. diff --git a/module/core/interval_adapter/Cargo.toml b/module/core/interval_adapter/Cargo.toml index 06b49ea870..218f3b9d96 100644 --- a/module/core/interval_adapter/Cargo.toml +++ b/module/core/interval_adapter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "interval_adapter" -version = "0.18.0" +version = "0.19.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/interval_adapter/Readme.md b/module/core/interval_adapter/Readme.md index f1d787b680..733185aeeb 100644 --- a/module/core/interval_adapter/Readme.md +++ b/module/core/interval_adapter/Readme.md @@ -2,8 +2,7 @@ # Module :: interval_adapter - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml)[![docs.rs](https://img.shields.io/docsrs/interval_adapter?color=e3e8f0&logo=docs.rs)](https://docs.rs/interval_adapter)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Finterval_adapter_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20interval_adapter_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_interval_adapter_push.yml) [![docs.rs](https://img.shields.io/docsrs/interval_adapter?color=e3e8f0&logo=docs.rs)](https://docs.rs/interval_adapter) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/interval_adapter/examples/interval_adapter_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/interval_adapter/examples/interval_adapter_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Integer interval adapter for both Range and RangeInclusive. diff --git a/module/core/is_slice/Cargo.toml b/module/core/is_slice/Cargo.toml index 745812aa3c..b3cb822caa 100644 --- a/module/core/is_slice/Cargo.toml +++ b/module/core/is_slice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "is_slice" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/is_slice/Readme.md b/module/core/is_slice/Readme.md index ec18428049..a2eeea5825 100644 --- a/module/core/is_slice/Readme.md +++ b/module/core/is_slice/Readme.md @@ -2,8 +2,7 @@ # Module :: is_slice - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml)[![docs.rs](https://img.shields.io/docsrs/is_slice?color=e3e8f0&logo=docs.rs)](https://docs.rs/is_slice)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fis_slice_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20is_slice_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_is_slice_push.yml) [![docs.rs](https://img.shields.io/docsrs/is_slice?color=e3e8f0&logo=docs.rs)](https://docs.rs/is_slice) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/is_slice/examples/is_slice_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/is_slice/examples/is_slice_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Macro to answer the question: is it a slice? diff --git a/module/core/is_slice/examples/is_slice_trivial_sample.rs b/module/core/is_slice/examples/is_slice_trivial.rs similarity index 92% rename from module/core/is_slice/examples/is_slice_trivial_sample.rs rename to module/core/is_slice/examples/is_slice_trivial.rs index d052a0f2ee..80b5b21aa3 100644 --- a/module/core/is_slice/examples/is_slice_trivial_sample.rs +++ b/module/core/is_slice/examples/is_slice_trivial.rs @@ -1,5 +1,7 @@ //! qqq : write proper descriptionuse is_slice::*; +use is_slice::is_slice; + fn main() { diff --git a/module/core/iter_tools/Cargo.toml b/module/core/iter_tools/Cargo.toml index 9c5790abbc..077198d0ce 100644 --- a/module/core/iter_tools/Cargo.toml +++ b/module/core/iter_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iter_tools" -version = "0.15.0" +version = "0.16.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/iter_tools/Readme.md b/module/core/iter_tools/Readme.md index 85a7cc830d..30ef42b2d2 100644 --- a/module/core/iter_tools/Readme.md +++ b/module/core/iter_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: iter_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/iter_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/iter_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fiter_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20iter_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_iter_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/iter_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/iter_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/iter_tools/examples/iter_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/iter_tools/examples/iter_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools to iterate. Currently it simply reexports itertools. diff --git a/module/core/iter_tools/examples/iter_tools_trivial.rs b/module/core/iter_tools/examples/iter_tools_trivial.rs index 2dfe3b101b..01ed1630e7 100644 --- a/module/core/iter_tools/examples/iter_tools_trivial.rs +++ b/module/core/iter_tools/examples/iter_tools_trivial.rs @@ -1,27 +1,35 @@ -//! qqq : write proper description - +//! This example demonstrates the usage of some standard and non-standard functions +//! from the `iter_tools` crate. The `iter_tools` crate provides additional iterator +//! methods beyond those provided by the standard library. #[ cfg( not( feature = "enabled" ) ) ] fn main() {} #[ cfg( feature = "enabled" ) ] fn main() { + // Importing functions from the `iter_tools` crate use iter_tools::*; /* standard functions */ + // Creating a vector let vec = vec![ 5, 1, -2 ]; + // Finding the minimum value in the vector let min = min( &vec ); assert_eq!( *min.unwrap(), -2 ); /* non standard functions */ + // Creating another vector let vec = vec![ 5, 1, -2 ]; - let added = vec![ "a", "b", "c" ]; + // Initializing an empty vector to store the result let mut result = vec![]; - let zipped = zip( &vec, &added ); - for( left, right ) in zipped + // Reversing the vector using the `rev` function from `iter_tools` + let reversed = rev( &vec ); + // Iterating over the reversed vector + for v in reversed { - result.push( ( *left, *right ) ); + // Pushing the dereferenced value into the result vector + result.push( *v ); } - assert_eq!( result, vec![ ( 5, "a" ), ( 1, "b" ), ( -2, "c" ) ] ); + assert_eq!( result, vec![ -2, 1, 5, ] ); } diff --git a/module/core/macro_tools/Cargo.toml b/module/core/macro_tools/Cargo.toml index e6ba7e57a0..f52852c399 100644 --- a/module/core/macro_tools/Cargo.toml +++ b/module/core/macro_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "macro_tools" -version = "0.23.0" +version = "0.24.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/macro_tools/Readme.md b/module/core/macro_tools/Readme.md index bdc3ed9217..98396be845 100644 --- a/module/core/macro_tools/Readme.md +++ b/module/core/macro_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: proc_macro_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/macro_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmacro_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20macro_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_macro_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/macro_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/macro_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/macro_tools/examples/macro_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/macro_tools/examples/macro_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing procedural macros. diff --git a/module/core/macro_tools/src/attr.rs b/module/core/macro_tools/src/attr.rs index 51a3fbe10d..c3a3cc2707 100644 --- a/module/core/macro_tools/src/attr.rs +++ b/module/core/macro_tools/src/attr.rs @@ -97,6 +97,108 @@ pub( crate ) mod private return Ok( false ) } + /// Checks if the given attribute name is a standard Rust attribute. + /// + /// Standard Rust attributes are those which are recognized and processed + /// directly by the Rust compiler. They influence various aspects of compilation, + /// including but not limited to conditional compilation, optimization hints, + /// code visibility, and procedural macro behavior. + /// + /// This function is useful when developing tools that need to interact with or + /// understand the significance of specific attributes in Rust source code, such + /// as linters, code analyzers, or procedural macros. + /// + /// This function does not cover all possible attributes but includes many of the + /// common ones that are relevant to most Rust projects. Developers are encouraged + /// to update this function as needed to suit more specialized needs, especially + /// when dealing with nightly-only compiler attributes or deprecated ones. + /// + /// # Parameters + /// - `attr_name`: A string slice that holds the name of the attribute to check. + /// + /// # Returns + /// Returns `true` if `attr_name` is a recognized standard Rust attribute. Otherwise, + /// returns `false`. + /// + /// # Examples + /// + /// Standard attributes: + /// + /// ``` + /// assert_eq!( macro_tools::attr::is_standard( "cfg" ), true ); + /// assert_eq!( macro_tools::attr::is_standard( "inline" ), true ); + /// assert_eq!( macro_tools::attr::is_standard( "derive" ), true ); + /// ``` + /// + /// Non-standard or custom attributes: + /// + /// ``` + /// assert_eq!( macro_tools::attr::is_standard( "custom_attr" ), false ); + /// assert_eq!( macro_tools::attr::is_standard( "my_attribute" ), false ); + /// ``` + /// + + pub fn is_standard<'a>( attr_name : &'a str ) -> bool + { + match attr_name + { + // Conditional compilation + "cfg" | "cfg_attr" => true, + + // Compiler instructions and optimizations + "inline" | "repr" | "derive" | "allow" | "warn" | "deny" | "forbid" => true, + + // Testing attributes + "test" | "bench" => true, + + // Documentation attributes + "doc" => true, + + // Visibility and accessibility + "pub" => true, // This would typically need context to be accurate + + // Safety and ABI + "unsafe" | "no_mangle" | "extern" => true, + + // Module and Crate configuration + "path" | "macro_use" | "crate_type" | "crate_name" => true, + + // Linking + "link" | "link_name" | "link_section" => true, + + // Usage warnings + "must_use" => true, + + // Other attributes + "cold" | "export_name" | "global_allocator" => true, + + // Module handling + "used" | "unused" => true, + + // Procedural macros and hygiene + "proc_macro" | "proc_macro_derive" | "proc_macro_attribute" => true, + + // Stability attributes + "stable" | "unstable" | "rustc_const_unstable" | "rustc_const_stable" | + "rustc_diagnostic_item" | "rustc_deprecated" | "rustc_legacy_const_generics" => true, + + // Special compiler attributes + "feature" | "non_exhaustive" => true, + + // Future compatibility + "rustc_paren_sugar" | "rustc_insignificant_dtor" => true, + + // Type system extensions + "opaque" => true, + + // Miscellaneous + "track_caller" => true, + + // Default case + _ => false, + } + } + /// /// Attribute which is inner. /// @@ -316,6 +418,7 @@ pub mod exposed { equation, has_debug, + is_standard, AttributesInner, AttributesOuter, AttributedIdent, @@ -326,4 +429,3 @@ pub mod exposed pub mod prelude { } - diff --git a/module/core/macro_tools/src/derive.rs b/module/core/macro_tools/src/derive.rs new file mode 100644 index 0000000000..9f29444e48 --- /dev/null +++ b/module/core/macro_tools/src/derive.rs @@ -0,0 +1,104 @@ +//! +//! Macro helpers around derive macro and structure [`syn::DeriveInput`]. +//! + +/// Internal namespace. +pub( crate ) mod private +{ + use super::super::*; + use syn::punctuated::Punctuated; + + /// + /// Extracts the named fields from a struct defined in a `syn::DeriveInput`. + /// + /// This function specifically handles `syn::DeriveInput` that represent structs + /// with named fields. It will return an error if the provided AST does not conform to these expectations. + /// + /// # Example + /// + /// ```rust, ignore + /// let ast = match syn::parse::< syn::DeriveInput >( input ) + /// { + /// Ok( syntax_tree ) => syntax_tree, + /// Err( err ) => return Err( err ), + /// }; + /// let fields = derive.named_fields( &ast ); + /// ``` + + pub fn named_fields< 'a >( ast : &'a syn::DeriveInput ) -> crate::Result< &'a Punctuated< syn::Field, syn::token::Comma > > + { + + let fields = match ast.data + { + syn::Data::Struct( ref data_struct ) => match data_struct.fields + { + syn::Fields::Named( ref fields_named ) => + { + &fields_named.named + }, + _ => return Err( syn_err!( ast, "Unknown format of data, expected syn::Fields::Named( ref fields_named )\n {}", qt!{ #ast } ) ), + }, + _ => return Err( syn_err!( ast, "Unknown format of data, expected syn::Data::Struct( ref data_struct )\n {}", qt!{ #ast } ) ), + }; + + Ok( fields ) + } + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; + +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + named_fields, + }; + +} + +/// Parented namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; +} + +/// Exposed namespace of the module. +pub mod exposed +{ + pub use super::protected as derive; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::prelude::*; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + }; + +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude +{ + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + }; + +} diff --git a/module/core/macro_tools/src/diag.rs b/module/core/macro_tools/src/diag.rs index f35ab49712..739d92f527 100644 --- a/module/core/macro_tools/src/diag.rs +++ b/module/core/macro_tools/src/diag.rs @@ -86,39 +86,43 @@ pub( crate ) mod private result } - /// Formats a debugging report for a pair of token streams, showing the original and generated code. + /// Formats a debugging report for code transformation processes, detailing both the original and generated code for easy comparison and review. /// - /// This function takes two inputs: the original code as an `IntoTokens` (which can be converted into a `proc_macro2::TokenStream`), - /// and the generated code as a `proc_macro2::TokenStream`. It formats both inputs with indentation for better readability, - /// labeling them as "original" and "generated" respectively. + /// This function creates a structured report comprising the initial input code, the resulting generated code, and an explanatory context. It is designed to facilitate debugging and documentation of code transformations, such as those performed in procedural macros or similar code generation tasks. The report categorizes the information into labeled sections to enhance readability and traceability. /// - /// Ensure the correct conversion of `proc_macro::TokenStream` to `proc_macro2::TokenStream` where necessary, - /// especially when interfacing with procedural macros' `input` parameter + /// This function helps visualize the changes from the original to the generated code, assisting developers in verifying and understanding the transformations applied during code generation processes. /// /// # Parameters - /// - `input`: The original input code that can be converted into a `proc_macro2::TokenStream`. - /// - `output`: The generated code as a `proc_macro2::TokenStream`. /// - /// # Returns - /// A `String` containing the formatted debug report. + /// - `about` : A description or context explaining the purpose or nature of the transformation. This information is displayed at the beginning of the report to provide an overview of the code transformation context. + /// - `input` : The original code before transformation. This is typically the code that is subject to processing by macros or other code generation tools. + /// - `output` : The code generated as a result of the transformation. This reflects the changes or enhancements made to the original code. /// /// # Type Parameters - /// - `IntoTokens`: A type that can be converted into a `proc_macro2::TokenStream`. + /// + /// - `IntoAbout` : A type that can be converted into a string representation, providing a descriptive context for the report. + /// - `IntoInput` : A type representing the original code, which can be converted into a string format for display. + /// - `IntoOutput` : A type representing the generated code, which can be converted into a string format for display. + /// + /// # Returns + /// + /// A string containing the formatted debug report, organized into sections with appropriate labels and indentation to distinguish between the original and generated code segments. /// /// # Examples + /// /// ``` /// use macro_tools::exposed::*; /// - /// let original_input : proc_macro2::TokenStream = qt! + /// let original_input : proc_macro2::TokenStream = quote! /// { - /// #[ derive( Debug, PartialEq ) ] + /// #[derive(Debug, PartialEq)] /// pub struct MyStruct /// { /// pub field : i32, /// } /// }; /// - /// let generated_code : proc_macro2::TokenStream = qt! + /// let generated_code : proc_macro2::TokenStream = quote! /// { /// impl MyStruct /// { @@ -130,52 +134,55 @@ pub( crate ) mod private /// }; /// /// // Format the debug report for printing or logging - /// let formatted_report = debug_report_format( "derive :: MyDerive", original_input, &generated_code ); + /// let formatted_report = report_format( "Code Transformation for MyStruct", original_input, generated_code ); /// println!( "{}", formatted_report ); /// ``` /// - /// This will output a formatted report showing the original input code and the generated code side by side, - /// each line indented for clarity. - /// - pub fn debug_report_format< IntoAbout, IntoTokens > + + pub fn report_format< IntoAbout, IntoInput, IntoOutput > ( - about : IntoAbout, input : IntoTokens, output : &proc_macro2::TokenStream + about : IntoAbout, input : IntoInput, output : IntoOutput ) -> String where - IntoAbout : Into< String >, - // xxx : qqq : use AsRef<> - IntoTokens : Into< proc_macro2::TokenStream >, + IntoAbout : ToString, + IntoInput : ToString, + IntoOutput : ToString, { format!( "\n" ) + - &format!( " = context\n\n{}\n\n", indentation( " ", about.into(), "" ) ) + - &format!( " = original\n\n{}\n\n", indentation( " ", input.into().to_string(), "" ) ) + - &format!( " = generated\n\n{}\n", indentation( " ", qt!{ #output }.to_string(), "" ) ) + &format!( " = context\n\n{}\n\n", indentation( " ", about.to_string(), "" ) ) + + &format!( " = original\n\n{}\n\n", indentation( " ", input.to_string(), "" ) ) + + &format!( " = generated\n\n{}\n", indentation( " ", output.to_string(), "" ) ) } /// Prints a debugging report for a pair of token streams to the standard output. /// - /// This convenience function wraps `debug_report_format`, directly printing the formatted report to stdout. - /// It serves as a utility for debugging procedural macros, providing a clear comparison between original - /// and generated code. + /// This function acts as a utility for debugging transformations in procedural macros or other code generation scenarios. + /// It provides an immediate visual comparison of the original code versus the generated code by utilizing the `report_format` + /// function to format the output and then printing it directly to the standard output. This can be particularly helpful for + /// real-time debugging and quick assessments without requiring additional output management. /// /// # Parameters and Type Parameters - /// - Same as `debug_report_format`. + /// - `about` : A description of the code transformation context or operation. This is used to headline the generated report. + /// - `input` : The original code or token stream before transformation. This is what the code looked like prior to any procedural manipulations. + /// - `output` : The transformed or generated code or token stream as a result of the macro or code transformation process. + /// + /// The types for these parameters are expected to be convertible to strings, matching the `report_format` function's requirements. /// /// # Examples /// - /// ``` + /// ```rust /// use macro_tools::exposed::*; /// - /// let original_input : proc_macro2::TokenStream = qt! + /// let original_input : proc_macro2::TokenStream = quote! /// { - /// #[ derive( Debug, PartialEq ) ] + /// #[derive(Debug, PartialEq)] /// pub struct MyStruct /// { /// pub field : i32, /// } /// }; /// - /// let generated_code : proc_macro2::TokenStream = qt! + /// let generated_code : proc_macro2::TokenStream = quote! /// { /// impl MyStruct /// { @@ -187,21 +194,23 @@ pub( crate ) mod private /// }; /// /// // Directly print the debug report - /// debug_report_print( "derive :: MyDerive", original_input, &generated_code ); + /// report_print( "Code Transformation for MyStruct", original_input, generated_code ); /// ``` /// - /// This will output a formatted report showing the original input code and the generated code side by side, - /// each line indented for clarity. + /// The above example demonstrates how the `report_print` function can be used to visualize the changes from original input code to the generated code, + /// helping developers to verify and understand the modifications made during code generation processes. The output is formatted to show clear distinctions + /// between the 'original' and 'generated' sections, providing an easy-to-follow comparison. - pub fn debug_report_print< IntoAbout, IntoTokens > + pub fn report_print< IntoAbout, IntoInput, IntoOutput > ( - about : IntoAbout, input : IntoTokens, output : &proc_macro2::TokenStream + about : IntoAbout, input : IntoInput, output : IntoOutput ) where - IntoAbout : Into< String >, - IntoTokens : Into< proc_macro2::TokenStream >, + IntoAbout : ToString, + IntoInput : ToString, + IntoOutput : ToString, { - println!( "{}", debug_report_format( about, input, output ) ); + println!( "{}", report_format( about, input, output ) ); } /// @@ -408,8 +417,8 @@ pub mod exposed { Result, indentation, - debug_report_format, - debug_report_print, + report_format, + report_print, }; } diff --git a/module/core/macro_tools/src/generic_args.rs b/module/core/macro_tools/src/generic_args.rs new file mode 100644 index 0000000000..aeea032b5a --- /dev/null +++ b/module/core/macro_tools/src/generic_args.rs @@ -0,0 +1,191 @@ +//! +//! Manipulations on generic arguments. +//! +//! xxx : update documentation of file + +/// Internal namespace. +pub( crate ) mod private +{ + + /// A trait for converting a reference to an existing type into a `syn::AngleBracketedGenericArguments`. + /// + /// This trait provides a mechanism to transform various types that represent generic parameters, + /// such as `syn::Generics`, into a uniform `syn::AngleBracketedGenericArguments`. This is particularly + /// useful when working with Rust syntax trees in procedural macros, allowing for the manipulation + /// and merging of generic parameters from different syntactic elements. + pub trait IntoGenericArgs + { + /// Converts a reference of the implementing type into `syn::AngleBracketedGenericArguments`. + /// + /// This method should handle the conversion logic necessary to transform the implementing + /// type's generic parameter representations into the structured format required by + /// `syn::AngleBracketedGenericArguments`, which is commonly used to represent generic parameters + /// enclosed in angle brackets. + /// + /// # Returns + /// A new instance of `syn::AngleBracketedGenericArguments` representing the generic parameters + /// of the original type. + fn into_generic_args( &self ) -> syn::AngleBracketedGenericArguments; + } + + impl IntoGenericArgs for syn::Generics + { + fn into_generic_args( &self ) -> syn::AngleBracketedGenericArguments + { + let args = self.params.iter().map( | param | + { + match param + { + syn::GenericParam::Type( ty ) => syn::GenericArgument::Type( syn::Type::Path( syn::TypePath + { + qself: None, + path: ty.ident.clone().into(), + })), + syn::GenericParam::Lifetime( lifetime ) => syn::GenericArgument::Lifetime( lifetime.lifetime.clone() ), + syn::GenericParam::Const( const_param ) => syn::GenericArgument::Const( syn::Expr::Path( syn::ExprPath + { + attrs: vec![], + qself: None, + path: const_param.ident.clone().into(), + })), + } + }).collect(); + + syn::AngleBracketedGenericArguments + { + colon2_token: None, + lt_token: syn::token::Lt::default(), + args, + gt_token: syn::token::Gt::default(), + } + } + } + + /// Merges two `syn::AngleBracketedGenericArguments` instances into a new one, + /// prioritizing lifetime parameters before other types of generic arguments. + /// + /// This function takes two references to `syn::AngleBracketedGenericArguments` and + /// categorizes their arguments into lifetimes and other types. It then combines + /// them such that all lifetimes from both instances precede any other arguments in the + /// resulting `syn::AngleBracketedGenericArguments` instance. This is particularly useful + /// for ensuring that the merged generics conform to typical Rust syntax requirements where + /// lifetimes are declared before other generic parameters. + /// + /// # Arguments + /// + /// * `a` - A reference to the first `syn::AngleBracketedGenericArguments` instance, containing one or more generic arguments. + /// * `b` - A reference to the second `syn::AngleBracketedGenericArguments` instance, containing one or more generic arguments. + /// + /// # Returns + /// + /// Returns a new `syn::AngleBracketedGenericArguments` instance containing the merged + /// arguments from both `a` and `b`, with lifetimes appearing first. + /// + /// # Examples + /// + /// ``` + /// use macro_tools::{ + /// generic_args, + /// syn::{parse_quote, AngleBracketedGenericArguments}, + /// }; + /// + /// let a: AngleBracketedGenericArguments = parse_quote! { <'a, T: Clone, U: Default> }; + /// let b: AngleBracketedGenericArguments = parse_quote! { <'b, V: core::fmt::Debug> }; + /// let merged = generic_args::merge(&a, &b); + /// + /// let expected: AngleBracketedGenericArguments = parse_quote! { <'a, 'b, T: Clone, U: Default, V: core::fmt::Debug> }; + /// assert_eq!(merged, expected); + /// ``` + /// + /// This example demonstrates how lifetimes `'a` and `'b` are placed before other generic parameters + /// like `T`, `U`, and `V` in the merged result, adhering to the expected syntax order in Rust generics. + pub fn merge + ( + a : &syn::AngleBracketedGenericArguments, + b : &syn::AngleBracketedGenericArguments + ) -> syn::AngleBracketedGenericArguments + { + let mut lifetimes : syn::punctuated::Punctuated< syn::GenericArgument, syn::token::Comma > = syn::punctuated::Punctuated::new(); + let mut others : syn::punctuated::Punctuated< syn::GenericArgument, syn::token::Comma > = syn::punctuated::Punctuated::new(); + + // Function to categorize and collect arguments into lifetimes and others + let mut categorize_and_collect = |args : &syn::punctuated::Punctuated| + { + for arg in args.iter() + { + match arg + { + syn::GenericArgument::Lifetime( _ ) => lifetimes.push( arg.clone() ), + _ => others.push( arg.clone() ), + } + } + }; + + // Categorize and collect from both input arguments + categorize_and_collect( &a.args ); + categorize_and_collect( &b.args ); + + // Combine lifetimes and other arguments into final merged arguments + let mut args = syn::punctuated::Punctuated::new(); + args.extend( lifetimes ); + args.extend( others ); + + syn::AngleBracketedGenericArguments + { + colon2_token: None, // Adjust if needed based on context + lt_token: syn::token::Lt::default(), + args, + gt_token: syn::token::Gt::default(), + } + } + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; + +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + merge, + }; +} + +/// Orphan namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + IntoGenericArgs, + }; +} + +/// Exposed namespace of the module. +pub mod exposed +{ + pub use super::protected as generic_args; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super:: + { + prelude::*, + }; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude +{ +} diff --git a/module/core/macro_tools/src/generic_params.rs b/module/core/macro_tools/src/generic_params.rs new file mode 100644 index 0000000000..f2e852f125 --- /dev/null +++ b/module/core/macro_tools/src/generic_params.rs @@ -0,0 +1,535 @@ +//! +//! Manipulations on generic parameters. +//! +//! # Example of generic parameters +//! +//!```rust +//! +//! pub struct CommandFormer< K, Context = () > +//! where +//! K : core::hash::Hash + std::cmp::Eq, +//! { +//! properties : core::option::Option< std::collections::HashMap< K, String > >, +//! _phantom : core::marker::PhantomData< Context >, +//! } +//! +//! impl< K, Context > +//! CommandFormer< K, Context > +//! where +//! K : core::hash::Hash + std::cmp::Eq, +//! {} +//!``` +//! xxx : update documentation of file + +/// Internal namespace. +pub( crate ) mod private +{ + use super::super::*; + + /// A `GenericsWithWhere` struct to handle the parsing of Rust generics with an explicit `where` clause. + /// + /// This wrapper addresses the limitation in the `syn` crate where parsing `Generics` directly from a `ParseStream` + /// does not automatically handle associated `where` clauses. By integrating `where` clause parsing into the + /// `GenericsWithWhere`, this struct provides a seamless way to capture both the generics and their constraints + /// in scenarios where the `where` clause is crucial for type constraints and bounds in Rust macros and code generation. + /// + /// Usage: + /// ``` + /// let parsed_generics : macro_tools::GenericsWithWhere = syn::parse_str( "< T : Clone, U : Default = Default1 > where T : Default").unwrap(); + /// assert!( parsed_generics.generics.params.len() == 2 ); + /// assert!( parsed_generics.generics.where_clause.is_some() ); + /// ``` + /// + + #[ derive( Debug ) ] + pub struct GenericsWithWhere + { + /// Syn's generics parameters. + pub generics : syn::Generics, + } + + impl GenericsWithWhere + { + /// Unwraps the `GenericsWithWhere` to retrieve the inner `syn::Generics`. + pub fn unwrap( self ) -> syn::Generics + { + self.generics + } + + /// Parses a string to a `GenericsWithWhere`, specifically designed to handle generics syntax with where clauses effectively. + pub fn parse_from_str( s : &str ) -> syn::Result< GenericsWithWhere > + { + syn::parse_str::< GenericsWithWhere >( s ) + } + } + + impl syn::parse::Parse for GenericsWithWhere + { + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + let generics : syn::Generics = input.parse()?; + let where_clause : Option< syn::WhereClause > = input.parse()?; + + let mut generics_clone = generics.clone(); + generics_clone.where_clause = where_clause; + + Ok( GenericsWithWhere + { + generics : generics_clone, + }) + } + } + + impl quote::ToTokens for GenericsWithWhere + { + fn to_tokens( &self, tokens : &mut proc_macro2::TokenStream ) + { + self.generics.to_tokens( tokens ); + } + } + + impl From for syn::Generics + { + fn from( g : GenericsWithWhere ) -> Self + { + g.generics + } + } + + impl From for GenericsWithWhere + { + fn from( generics : syn::Generics ) -> Self + { + GenericsWithWhere { generics } + } + } + + /// Merges two `syn::Generics` instances into a new one. + /// + /// This function takes two references to `syn::Generics` and combines their + /// type parameters and where clauses into a new `syn::Generics` instance. If + /// both instances have where clauses, the predicates of these clauses are merged + /// into a single where clause. + /// + /// # Arguments + /// + /// * `a` - A reference to the first `syn::Generics` instance. + /// * `b` - A reference to the second `syn::Generics` instance. + /// + /// # Returns + /// + /// Returns a new `syn::Generics` instance containing the merged type parameters + /// and where clauses from `a` and `b`. + /// + /// # Examples + /// + /// + /// # use syn::{Generics, parse_quote}; + /// + /// let mut generics_a : syn::Generics = parse_quote!{ < T : Clone, U : Default > }; + /// generics_a.where_clause = parse_quote!{ where T : Default }; + /// let mut generics_b : syn::Generics = parse_quote!{ < V : core::fmt::Debug > }; + /// generics_b.where_clause = parse_quote!{ where V : Sized }; + /// let got = generic_params::merge( &generics_a, &generics_b ); + /// + /// let mut exp : syn::Generics = parse_quote! + /// { + /// < T : Clone, U : Default, V : core::fmt::Debug > + /// }; + /// exp.where_clause = parse_quote! + /// { + /// where + /// T : Default, + /// V : Sized + /// }; + /// + /// assert_eq!( got, exp ); + + pub fn merge( a : &syn::Generics, b : &syn::Generics ) -> syn::Generics + { + + let mut result = syn::Generics + { + params : Default::default(), + where_clause : None, + lt_token : Some( syn::token::Lt::default() ), + gt_token : Some( syn::token::Gt::default() ), + }; + + // Merge params + // result.params.extend( a.params.iter().chain( b.params.iter() ) ); + for param in &a.params + { + result.params.push( param.clone() ); + } + for param in &b.params + { + result.params.push( param.clone() ); + } + + // Merge where clauses + result.where_clause = match( &a.where_clause, &b.where_clause ) + { + ( Some( a_clause ), Some( b_clause ) ) => + { + let mut merged_where_clause = syn::WhereClause + { + where_token: a_clause.where_token, + predicates: a_clause.predicates.clone(), + }; + for predicate in &b_clause.predicates + { + merged_where_clause.predicates.push( predicate.clone() ); + } + Some( merged_where_clause ) + }, + ( Some( a_clause ), None ) => Some( a_clause.clone() ), + ( None, Some( b_clause ) ) => Some( b_clause.clone() ), + _ => None, + }; + + result + } + + /// Extracts parameter names from the given `Generics`, + /// dropping bounds, defaults, and the where clause. + /// + /// This function simplifies the generics to include only the names of the type parameters, + /// lifetimes, and const parameters, without any of their associated bounds or default values. + /// The resulting `Generics` will have an empty where clause. + /// + /// # Arguments + /// + /// * `generics` - The `Generics` instance from which to extract parameter names. + /// + /// # Returns + /// + /// Returns a new `Generics` instance containing only the names of the parameters. + /// + /// # Examples + /// + /// ```rust + /// # use macro_tools::syn::parse_quote; + /// + /// let mut generics : syn::Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > }; + /// generics.where_clause = parse_quote!{ where T: core::fmt::Debug }; + /// // let generics : Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > where T: core::fmt::Debug }; + /// let simplified_generics = macro_tools::generic_params::names( &generics ); + /// + /// assert_eq!( simplified_generics.params.len(), 4 ); // Contains T, U, 'a, and N + /// assert!( simplified_generics.where_clause.is_none() ); // Where clause is removed + /// ``` + + pub fn names( generics : &syn::Generics ) -> syn::Generics + { + // use syn::{ Generics, GenericParam, LifetimeDef, TypeParam, ConstParam }; + use syn::{ Generics, GenericParam, LifetimeParam, TypeParam, ConstParam }; + + let result = Generics + { + params : generics.params.iter().map( | param | match param + { + GenericParam::Type( TypeParam { ident, .. } ) => GenericParam::Type( TypeParam + { + attrs : Vec::new(), + ident : ident.clone(), + colon_token : None, + bounds : Default::default(), + eq_token : None, + default : None, + }), + GenericParam::Lifetime( LifetimeParam { lifetime, .. } ) => GenericParam::Lifetime( LifetimeParam + { + attrs : Vec::new(), + lifetime : lifetime.clone(), + colon_token : None, + bounds : Default::default(), + }), + GenericParam::Const( ConstParam { ident, ty, .. } ) => GenericParam::Const( ConstParam + { + attrs : Vec::new(), + const_token : Default::default(), + ident : ident.clone(), + colon_token : Default::default(), + ty : ty.clone(), + eq_token : Default::default(), + default : None, + }), + }).collect(), + where_clause : None, + lt_token : generics.lt_token, + gt_token : generics.gt_token, + }; + + result + } + + /// Decomposes `syn::Generics` into components suitable for different usage contexts in Rust implementations, + /// specifically focusing on different requirements for `impl` blocks and type definitions. + /// + /// This function prepares three versions of the generics: + /// - One preserving the full structure for `impl` declarations. + /// - One simplified for type definitions, removing bounds and defaults from type and const parameters, retaining only identifiers. + /// - One for the where clauses, if present, ensuring they are correctly punctuated. + /// + /// This helps in situations where you need different representations of generics for implementing traits, + /// defining types, or specifying trait bounds and conditions. + /// + /// # Examples + /// + /// ```rust + /// let code : syn::Generics = syn::parse_quote!{ <'a, T, const N : usize, U : Trait1> }; + /// let ( generics_with_defaults, generics_for_impl, generics_for_ty, generics_where ) = macro_tools::generic_params::decompose( &code ); + /// + /// // Use in a macro for generating code + /// macro_tools::qt! + /// { + /// impl < #generics_for_impl > MyTrait for Struct1 < #generics_for_ty > + /// where + /// #generics_where + /// { + /// // implementation details... + /// } + /// }; + /// ``` + /// + /// # Arguments + /// + /// * `generics` - A reference to the `syn::Generics` to be decomposed. + /// + /// # Returns + /// + /// Returns a tuple containing: + /// - `syn::punctuated::Punctuated`: Original generics with defaults, used where full specification is needed. + /// - `syn::punctuated::Punctuated`: Generics for `impl` blocks, retaining bounds but no defaults. + /// - `syn::punctuated::Punctuated`: Simplified generics for type definitions, only identifiers. + /// - `syn::punctuated::Punctuated`: Where clauses, properly punctuated for use in where conditions. + + pub fn decompose + ( + generics : &syn::Generics, + ) + -> + ( + syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, + syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, + ) + { + + let mut generics_with_defaults = generics.params.clone(); + punctuated::ensure_trailing_comma( &mut generics_with_defaults ); + + let mut generics_for_impl = syn::punctuated::Punctuated::new(); + let mut generics_for_ty = syn::punctuated::Punctuated::new(); + + // Process each generic parameter + for param in &generics.params + { + match param + { + syn::GenericParam::Type( type_param ) => + { + // Retain bounds for generics_for_impl, remove defaults + let impl_param = syn::GenericParam::Type( syn::TypeParam + { + attrs : vec![], + ident : type_param.ident.clone(), + colon_token : type_param.colon_token, + bounds : type_param.bounds.clone(), + eq_token : None, // Remove default token + default : None, // Remove default value + } ); + generics_for_impl.push_value( impl_param ); + generics_for_impl.push_punct( syn::token::Comma::default() ); + + // Simplify for generics_for_ty by removing all except identifiers + let ty_param = syn::GenericParam::Type( syn::TypeParam + { + attrs : vec![], + ident : type_param.ident.clone(), + colon_token : None, + bounds : syn::punctuated::Punctuated::new(), + eq_token : None, + default : None, + } ); + generics_for_ty.push_value( ty_param ); + generics_for_ty.push_punct( syn::token::Comma::default() ); + }, + syn::GenericParam::Const( const_param ) => + { + // Simplify const parameters by removing all details except the identifier + let impl_param = syn::GenericParam::Const( syn::ConstParam + { + attrs : vec![], + const_token : const_param.const_token, + ident : const_param.ident.clone(), + colon_token : const_param.colon_token, + ty : const_param.ty.clone(), + eq_token : None, + default : None, + } ); + generics_for_impl.push_value( impl_param ); + generics_for_impl.push_punct( syn::token::Comma::default() ); + + let ty_param = syn::GenericParam::Type( syn::TypeParam + { + attrs : vec![], + ident : const_param.ident.clone(), + colon_token : None, + bounds : syn::punctuated::Punctuated::new(), + eq_token : None, + default : None, + }); + generics_for_ty.push_value( ty_param ); + generics_for_ty.push_punct( syn::token::Comma::default() ); + }, + syn::GenericParam::Lifetime( lifetime_param ) => + { + // Lifetimes are added as-is to both generics_for_impl and generics_for_ty + generics_for_impl.push_value( syn::GenericParam::Lifetime( lifetime_param.clone() ) ); + generics_for_impl.push_punct( syn::token::Comma::default() ); + generics_for_ty.push_value( syn::GenericParam::Lifetime( lifetime_param.clone() ) ); + generics_for_ty.push_punct( syn::token::Comma::default() ); + } + } + } + + // Clone where predicates if present, ensuring they end with a comma + let generics_where = if let Some( where_clause ) = &generics.where_clause + { + let mut predicates = where_clause.predicates.clone(); + punctuated::ensure_trailing_comma( &mut predicates ); + predicates + } + else + { + syn::punctuated::Punctuated::new() + }; + + ( generics_with_defaults, generics_for_impl, generics_for_ty, generics_where ) + } + +// pub fn decompose +// ( +// generics : &syn::Generics +// ) +// -> +// ( +// syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, +// syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, +// syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, +// ) +// { +// let mut generics_for_impl = generics.params.clone(); +// punctuated::ensure_trailing_comma( &mut generics_for_impl ); +// +// let mut generics_for_ty = syn::punctuated::Punctuated::new(); +// for param in &generics.params +// { +// match param +// { +// syn::GenericParam::Type( type_param ) => +// { +// let simplified = syn::GenericParam::Type( syn::TypeParam +// { +// attrs : vec![], +// ident : type_param.ident.clone(), +// colon_token : None, +// bounds : syn::punctuated::Punctuated::new(), +// eq_token : None, +// default : None, +// }); +// generics_for_ty.push_value( simplified ); +// generics_for_ty.push_punct( syn::token::Comma::default() ); +// }, +// syn::GenericParam::Const( const_param ) => +// { +// let simplified = syn::GenericParam::Type( syn::TypeParam +// { +// attrs : vec![], +// ident : const_param.ident.clone(), +// colon_token : None, +// bounds : syn::punctuated::Punctuated::new(), +// eq_token : None, +// default : None, +// }); +// generics_for_ty.push_value( simplified ); +// generics_for_ty.push_punct( syn::token::Comma::default() ); +// }, +// syn::GenericParam::Lifetime( lifetime_param ) => +// { +// generics_for_ty.push_value( syn::GenericParam::Lifetime( lifetime_param.clone() ) ); +// generics_for_ty.push_punct( syn::token::Comma::default() ); +// } +// } +// } +// +// let generics_where = if let Some( where_clause ) = &generics.where_clause +// { +// let mut predicates = where_clause.predicates.clone(); +// punctuated::ensure_trailing_comma( &mut predicates ); +// predicates +// } +// else +// { +// syn::punctuated::Punctuated::new() +// }; +// +// ( generics_for_impl, generics_for_ty, generics_where ) +// } + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; + +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + merge, + names, + decompose, + }; +} + +// xxx : external attr instead of internal? +/// Orphan namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + GenericsWithWhere, + }; +} + +/// Exposed namespace of the module. +pub mod exposed +{ + pub use super::protected as generic_params; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super:: + { + prelude::*, + }; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude +{ +} diff --git a/module/core/macro_tools/src/generics.rs b/module/core/macro_tools/src/generics.rs deleted file mode 100644 index 63f8496495..0000000000 --- a/module/core/macro_tools/src/generics.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! -//! Manipulations on generic parameters. -//! -//! # Example of generic parameters -//! -//!```rust -//! -//! pub struct CommandFormer< K, Context = () > -//! where -//! K : core::hash::Hash + std::cmp::Eq, -//! { -//! properties : core::option::Option< std::collections::HashMap< K, String > >, -//! _phantom : core::marker::PhantomData< Context >, -//! } -//! -//! impl< K, Context > -//! CommandFormer< K, Context > -//! where -//! K : core::hash::Hash + std::cmp::Eq, -//! {} -//!``` - -/// Internal namespace. -pub( crate ) mod private -{ - - /// Merges two `syn::Generics` instances into a new one. - /// - /// This function takes two references to `syn::Generics` and combines their - /// type parameters and where clauses into a new `syn::Generics` instance. If - /// both instances have where clauses, the predicates of these clauses are merged - /// into a single where clause. - /// - /// # Arguments - /// - /// * `a` - A reference to the first `syn::Generics` instance. - /// * `b` - A reference to the second `syn::Generics` instance. - /// - /// # Returns - /// - /// Returns a new `syn::Generics` instance containing the merged type parameters - /// and where clauses from `a` and `b`. - /// - /// # Examples - /// - /// - /// # use syn::{Generics, parse_quote}; - /// - /// let mut generics_a : syn::Generics = parse_quote!{ < T : Clone, U : Default > }; - /// generics_a.where_clause = parse_quote!{ where T : Default }; - /// let mut generics_b : syn::Generics = parse_quote!{ < V : std::fmt::Debug > }; - /// generics_b.where_clause = parse_quote!{ where V : Sized }; - /// let got = generics::merge( &generics_a, &generics_b ); - /// - /// let mut exp : syn::Generics = parse_quote! - /// { - /// < T : Clone, U : Default, V : std::fmt::Debug > - /// }; - /// exp.where_clause = parse_quote! - /// { - /// where - /// T : Default, - /// V : Sized - /// }; - /// - /// assert_eq!( got, exp ); - - pub fn merge( a : &syn::Generics, b : &syn::Generics ) -> syn::Generics - { - - let mut result = syn::Generics - { - params : Default::default(), - where_clause : None, - lt_token : Some( syn::token::Lt::default() ), - gt_token : Some( syn::token::Gt::default() ), - }; - - // Merge params - for param in &a.params - { - result.params.push( param.clone() ); - } - for param in &b.params - { - result.params.push( param.clone() ); - } - - // Merge where clauses - result.where_clause = match( &a.where_clause, &b.where_clause ) - { - ( Some( a_clause ), Some( b_clause ) ) => - { - let mut merged_where_clause = syn::WhereClause - { - where_token: a_clause.where_token, - predicates: a_clause.predicates.clone(), - }; - for predicate in &b_clause.predicates - { - merged_where_clause.predicates.push( predicate.clone() ); - } - Some( merged_where_clause ) - }, - ( Some( a_clause ), None ) => Some( a_clause.clone() ), - ( None, Some( b_clause ) ) => Some( b_clause.clone() ), - _ => None, - }; - - result - } - - /// Extracts parameter names from the given `Generics`, - /// dropping bounds, defaults, and the where clause. - /// - /// This function simplifies the generics to include only the names of the type parameters, - /// lifetimes, and const parameters, without any of their associated bounds or default values. - /// The resulting `Generics` will have an empty where clause. - /// - /// # Arguments - /// - /// * `generics` - The `Generics` instance from which to extract parameter names. - /// - /// # Returns - /// - /// Returns a new `Generics` instance containing only the names of the parameters. - /// - /// # Examples - /// - /// ```rust - /// # use macro_tools::syn::parse_quote; - /// - /// let mut generics : syn::Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > }; - /// generics.where_clause = parse_quote!{ where T: std::fmt::Debug }; - /// // let generics : Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > where T: std::fmt::Debug }; - /// let simplified_generics = macro_tools::generics::params_names( &generics ); - /// - /// assert_eq!( simplified_generics.params.len(), 4 ); // Contains T, U, 'a, and N - /// assert!( simplified_generics.where_clause.is_none() ); // Where clause is removed - /// ``` - - pub fn params_names( generics : &syn::Generics ) -> syn::Generics - { - // use syn::{ Generics, GenericParam, LifetimeDef, TypeParam, ConstParam }; - use syn::{ Generics, GenericParam, LifetimeParam, TypeParam, ConstParam }; - - let result = Generics - { - params : generics.params.iter().map( | param | match param - { - GenericParam::Type( TypeParam { ident, .. } ) => GenericParam::Type( TypeParam - { - attrs : Vec::new(), - ident : ident.clone(), - colon_token : None, - bounds : Default::default(), - eq_token : None, - default : None, - }), - GenericParam::Lifetime( LifetimeParam { lifetime, .. } ) => GenericParam::Lifetime( LifetimeParam - { - attrs : Vec::new(), - lifetime : lifetime.clone(), - colon_token : None, - bounds : Default::default(), - }), - GenericParam::Const( ConstParam { ident, ty, .. } ) => GenericParam::Const( ConstParam - { - attrs : Vec::new(), - const_token : Default::default(), - ident : ident.clone(), - colon_token : Default::default(), - ty : ty.clone(), - eq_token : Default::default(), - default : None, - }), - }).collect(), - where_clause : None, - lt_token : generics.lt_token, - gt_token : generics.gt_token, - }; - - result - } - -} - -#[ doc( inline ) ] -#[ allow( unused_imports ) ] -pub use protected::*; - -/// Protected namespace of the module. -pub mod protected -{ - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::orphan::*; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::private::merge; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::private::params_names; -} - -/// Orphan namespace of the module. -pub mod orphan -{ - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super::exposed::*; -} - -/// Exposed namespace of the module. -pub mod exposed -{ - pub use super::protected as generics; - #[ doc( inline ) ] - #[ allow( unused_imports ) ] - pub use super:: - { - prelude::*, - }; -} - -/// Prelude to use essentials: `use my_module::prelude::*`. -pub mod prelude -{ -} diff --git a/module/core/macro_tools/src/item.rs b/module/core/macro_tools/src/item.rs new file mode 100644 index 0000000000..64897375e7 --- /dev/null +++ b/module/core/macro_tools/src/item.rs @@ -0,0 +1,129 @@ +//! xxx : update documentation of file + +/// Internal namespace. +pub( crate ) mod private +{ + use super::super::*; + + /// Ensures the last field in a struct has a trailing comma. + /// + /// This function checks and modifies the fields of a given struct, `input`, ensuring that the last field, whether in + /// named or unnamed structs, ends with a trailing comma. This adjustment is commonly needed in macro-generated + /// code to maintain consistency and syntactical correctness across different struct types, including unit structs + /// which are unaffected as they do not contain fields. + /// + /// # Arguments + /// + /// * `input` - A reference to the struct (`syn::ItemStruct`) whose fields are to be checked and modified. + /// + /// # Returns + /// + /// Returns a modified clone of the input struct (`syn::ItemStruct`) where the last field in named or unnamed + /// structs has a trailing comma. Unit structs remain unchanged as they do not contain fields. + /// + /// # Examples + /// + /// ``` + /// use macro_tools:: + /// { + /// syn::{ parse_quote, ItemStruct }, + /// quote::quote, + /// }; + /// + /// // Create a struct using `parse_quote!` macro + /// let input_struct : ItemStruct = parse_quote! + /// { + /// struct Example + /// { + /// field1 : i32, + /// field2 : String + /// } + /// }; + /// + /// // Apply `ensure_comma` to ensure the last field has a trailing comma + /// let modified_struct = macro_tools::item::ensure_comma( &input_struct ); + /// + /// // Now `modified_struct` will have a trailing comma after `field2` + /// assert_eq!( quote!( #modified_struct ).to_string(), quote! + /// { + /// struct Example + /// { + /// field1 : i32, + /// field2 : String, + /// } + /// }.to_string() ); + /// ``` + + pub fn ensure_comma( input : &syn::ItemStruct ) -> syn::ItemStruct + { + let mut new_input = input.clone(); // Clone the input to modify it + + match &mut new_input.fields + { + // Handle named fields + syn::Fields::Named( syn::FieldsNamed { named, .. } ) => + { + punctuated::ensure_trailing_comma( named ) + }, + // Handle unnamed fields (tuples) + syn::Fields::Unnamed( syn::FieldsUnnamed { unnamed, .. } ) => + { + punctuated::ensure_trailing_comma( unnamed ) + }, + // Do nothing for unit structs + syn::Fields::Unit => {} + } + + new_input + } + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; + +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + ensure_comma, + }; +} + +// xxx : external attr instead of internal? +/// Orphan namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + }; +} + +/// Exposed namespace of the module. +pub mod exposed +{ + pub use super::protected as item; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super:: + { + prelude::*, + }; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude +{ +} diff --git a/module/core/macro_tools/src/lib.rs b/module/core/macro_tools/src/lib.rs index 6bf4f43554..d447b4b67b 100644 --- a/module/core/macro_tools/src/lib.rs +++ b/module/core/macro_tools/src/lib.rs @@ -3,26 +3,28 @@ #![ doc( html_root_url = "https://docs.rs/proc_macro_tools/latest/proc_macro_tools/" ) ] #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] +/// Modular files. #[ cfg( feature = "enabled" ) ] -pub mod attr; -#[ cfg( feature = "enabled" ) ] -pub mod container_kind; -#[ cfg( feature = "enabled" ) ] -pub mod diag; -#[ cfg( feature = "enabled" ) ] -pub mod generic_analyze; -#[ cfg( feature = "enabled" ) ] -pub mod generics; -#[ cfg( feature = "enabled" ) ] -pub mod name; -#[ cfg( feature = "enabled" ) ] -pub mod quantifier; -#[ cfg( feature = "enabled" ) ] -pub mod tokens; -#[ cfg( feature = "enabled" ) ] -pub mod typ; -#[ cfg( feature = "enabled" ) ] -pub mod type_struct; +#[ path = "." ] +mod file +{ + use super::*; + pub mod attr; + pub mod container_kind; + pub mod derive; + pub mod diag; + pub mod generic_analyze; + pub mod generic_args; + pub mod generic_params; + pub mod item; + pub mod name; + pub mod phantom; + pub mod punctuated; + pub mod quantifier; + pub mod tokens; + pub mod typ; + pub mod type_struct; +} /// /// Dependencies of the module. @@ -35,7 +37,6 @@ pub mod dependency pub use ::quote; pub use ::proc_macro2; pub use ::interval_adapter; - // pub use ::type_constructor; } #[ doc( inline ) ] @@ -52,12 +53,22 @@ pub mod protected pub use super:: { orphan::*, + }; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::file:: + { attr::orphan::*, container_kind::orphan::*, + derive::orphan::*, diag::orphan::*, generic_analyze::orphan::*, - generics::orphan::*, + generic_args::orphan::*, + generic_params::orphan::*, + item::orphan::*, name::orphan::*, + phantom::orphan::*, + punctuated::orphan::*, quantifier::orphan::*, tokens::orphan::*, typ::orphan::*, @@ -91,24 +102,27 @@ pub mod exposed pub use super:: { prelude::*, + }; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::file:: + { attr::exposed::*, container_kind::exposed::*, + derive::orphan::*, diag::exposed::*, generic_analyze::exposed::*, - generics::exposed::*, + generic_args::exposed::*, + generic_params::exposed::*, + item::exposed::*, name::exposed::*, + phantom::exposed::*, + punctuated::exposed::*, quantifier::exposed::*, tokens::exposed::*, typ::exposed::*, type_struct::exposed::*, }; - // #[ doc( inline ) ] - // #[ allow( unused_imports ) ] - // pub use super::quantifier:: - // { - // Pair, - // Many, - // }; } /// Prelude to use essentials: `use my_module::prelude::*`. @@ -157,14 +171,19 @@ pub mod prelude #[ doc( inline ) ] #[ allow( unused_imports ) ] - pub use super:: + pub use super::file:: { attr::prelude::*, container_kind::prelude::*, + derive::orphan::*, diag::prelude::*, generic_analyze::prelude::*, - generics::prelude::*, + generic_args::prelude::*, + generic_params::prelude::*, + item::prelude::*, name::prelude::*, + phantom::prelude::*, + punctuated::prelude::*, quantifier::prelude::*, tokens::prelude::*, typ::prelude::*, diff --git a/module/core/macro_tools/src/phantom.rs b/module/core/macro_tools/src/phantom.rs new file mode 100644 index 0000000000..7c9e3bbbda --- /dev/null +++ b/module/core/macro_tools/src/phantom.rs @@ -0,0 +1,224 @@ +//! xxx : update documentation of file + +/// Internal namespace. +pub( crate ) mod private +{ + use super::super::*; + + /// Adds a `PhantomData` field to a struct to manage generic parameter usage. + /// + /// This function clones a given `syn::ItemStruct`, calculates the appropriate `PhantomData` usage + /// based on the struct's generic parameters, and adds a corresponding `PhantomData` field. This field + /// helps in handling ownership and lifetime indications for generic parameters, ensuring that they + /// are correctly accounted for in type checking, even if they are not directly used in the struct's + /// fields. + /// + /// # Parameters + /// - `input`: A reference to the `syn::ItemStruct` which describes the structure to which the + /// `PhantomData` field will be added. + /// + /// # Returns + /// Returns a new `syn::ItemStruct` with the `PhantomData` field added to its list of fields. + /// + /// # Examples + /// ```rust + /// use syn::{ parse_quote, ItemStruct }; + /// + /// let input_struct: ItemStruct = parse_quote! + /// { + /// pub struct MyStruct< T, U > + /// { + /// data : T, + /// } + /// }; + /// + /// let modified_struct = macro_tools::phantom::add_to_item( &input_struct ); + /// println!( "{:#?}", modified_struct ); + /// + /// // Output will include a _phantom field of type `PhantomData< ( T, U ) >` + /// ``` + /// + + pub fn add_to_item( input : &syn::ItemStruct ) -> syn::ItemStruct + { + + // Only proceed if there are generics + if input.generics.params.is_empty() + { + return item::ensure_comma( input ); + } + + // Clone the input struct to work on a modifiable copy + let mut input = input.clone(); + + // Prepare the tuple type for PhantomData based on the struct's generics + let phantom = tuple( &input.generics.params ); + + // Handle different field types: Named, Unnamed, or Unit + match &mut input.fields + { + syn::Fields::Named( fields ) => + { + let phantom_field : syn::Field = syn::parse_quote! + { + _phantom : #phantom + }; + + // Ensure there is a trailing comma if fields are already present + if !fields.named.empty_or_trailing() + { + fields.named.push_punct( Default::default() ); + } + fields.named.push( phantom_field ); + fields.named.push_punct( Default::default() ); // Add trailing comma after adding PhantomData + }, + syn::Fields::Unnamed( fields ) => + { + let phantom_field : syn::Field = syn::parse_quote! + { + #phantom + }; + + // Ensure there is a trailing comma if fields are already present + if !fields.unnamed.empty_or_trailing() + { + fields.unnamed.push_punct( Default::default() ); + } + fields.unnamed.push_value( phantom_field ); + fields.unnamed.push_punct( Default::default() ); // Ensure to add the trailing comma after PhantomData + }, + syn::Fields::Unit => + { + // No fields to modify in a unit struct + } + }; + + input + } + + /// Constructs a `PhantomData` type tuple from the generic parameters of a struct. + /// + /// This function generates a tuple type for `PhantomData` using the given generic parameters, + /// which includes types, lifetimes, and const generics. It ensures that the generated tuple + /// use all parameters. + /// + /// # Parameters + /// - `input`: A reference to a `Punctuated< GenericParam, Comma>` containing the generic parameters. + /// + /// # Returns + /// Returns a `syn::Type` that represents a `PhantomData` tuple incorporating all the generic parameters. + /// + /// # Examples + /// ```rust + /// use syn::{parse_quote, punctuated::Punctuated, GenericParam, token::Comma}; + /// use macro_tools::phantom::tuple; + /// + /// let generics: Punctuated< GenericParam, Comma > = parse_quote! { 'a, T, const N : usize }; + /// let phantom_type = tuple( &generics ); + /// println!( "{}", quote::quote! { #phantom_type } ); + /// // Output: core::marker::PhantomData< ( &'a (), *const T, N ) > + /// ``` + /// + pub fn tuple( input : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma > ) -> syn::Type + { + use proc_macro2::Span; + use syn::{ GenericParam, Type }; + + // Prepare the tuple type for PhantomData based on the struct's generics + let generics_tuple_type = + { + let generics_list = input.iter().map( | param | + { + match param + { + GenericParam::Type( type_param ) => + { + let path = &type_param.ident; + let path2 : syn::Type = parse_quote!{ *const #path }; + path2 + }, + GenericParam::Lifetime( lifetime_param ) => Type::Reference( syn::TypeReference + { + and_token : Default::default(), + lifetime : Some( lifetime_param.lifetime.clone() ), + mutability : None, + elem : Box::new( Type::Tuple( syn::TypeTuple + { + paren_token : syn::token::Paren( Span::call_site() ), + elems : syn::punctuated::Punctuated::new(), + })), + }), + GenericParam::Const( const_param ) => Type::Path( syn::TypePath + { + qself : None, + path : const_param.ident.clone().into(), + }), + } + }).collect::< syn::punctuated::Punctuated< _, syn::token::Comma > >(); + + Type::Tuple( syn::TypeTuple + { + paren_token : syn::token::Paren( Span::call_site() ), + elems : generics_list, + }) + }; + + let result : syn::Type = syn::parse_quote! + { + core::marker::PhantomData< #generics_tuple_type > + }; + + result + } + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; + +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + add_to_item, + tuple, + }; +} + +// xxx : external attr instead of internal? +/// Orphan namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + }; +} + +/// Exposed namespace of the module. +pub mod exposed +{ + pub use super::protected as phantom; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super:: + { + prelude::*, + }; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude +{ +} diff --git a/module/core/macro_tools/src/punctuated.rs b/module/core/macro_tools/src/punctuated.rs new file mode 100644 index 0000000000..5ea50730c4 --- /dev/null +++ b/module/core/macro_tools/src/punctuated.rs @@ -0,0 +1,60 @@ +// ! xxx : write description + +/// Internal namespace. +pub( crate ) mod private +{ + + /// Ensures that a `syn::punctuated::Punctuated` collection ends with a comma if it contains elements. + pub fn ensure_trailing_comma< T : Clone > + ( punctuated : &mut syn::punctuated::Punctuated< T, syn::token::Comma > ) + { + if !punctuated.empty_or_trailing() + { + punctuated.push_punct( syn::token::Comma::default() ); + } + } + +} + +#[ doc( inline ) ] +#[ allow( unused_imports ) ] +pub use protected::*; + +/// Protected namespace of the module. +pub mod protected +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::orphan::*; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::private:: + { + ensure_trailing_comma, + }; +} + +/// Orphan namespace of the module. +pub mod orphan +{ + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super::exposed::*; +} + +/// Exposed namespace of the module. +pub mod exposed +{ + pub use super::protected as punctuated; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use super:: + { + prelude::*, + }; +} + +/// Prelude to use essentials: `use my_module::prelude::*`. +pub mod prelude +{ +} diff --git a/module/core/macro_tools/src/tokens.rs b/module/core/macro_tools/src/tokens.rs index b1740ad332..9f0cd32435 100644 --- a/module/core/macro_tools/src/tokens.rs +++ b/module/core/macro_tools/src/tokens.rs @@ -6,7 +6,7 @@ pub( crate ) mod private { use super::super::*; - use std::fmt; + use core::fmt; /// `Tokens` is a wrapper around `proc_macro2::TokenStream`. /// It is designed to facilitate the parsing and manipulation of token streams @@ -63,9 +63,9 @@ pub( crate ) mod private } } - impl std::fmt::Display for Tokens + impl core::fmt::Display for Tokens { - fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result { write!( f, "{}", self.inner.to_string() ) } @@ -137,9 +137,9 @@ pub( crate ) mod private } } - // impl std::fmt::Display for Equation + // impl core::fmt::Display for Equation // { - // fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + // fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result // { // write!( f, "{}", self.left.to_string() ); // write!( f, "{}", self.op.to_string() ); diff --git a/module/core/macro_tools/src/typ.rs b/module/core/macro_tools/src/typ.rs index 81b48b675b..34d45e32b3 100644 --- a/module/core/macro_tools/src/typ.rs +++ b/module/core/macro_tools/src/typ.rs @@ -90,6 +90,68 @@ pub( crate ) mod private vec![ ty ] } +// /// Extract generics from a type. +// pub fn all_type_parameters( type_example : &syn::Type ) +// -> +// Option< syn::punctuated::Punctuated< syn::GenericArgument, syn::token::Comma > > +// { +// if let syn::Type::Path( type_path ) = type_example +// { +// let segments = &type_path.path.segments; +// let last_segment = segments.last()?; +// +// if let syn::PathArguments::AngleBracketed( generics ) = &last_segment.arguments +// { +// return Some( generics.args.clone() ); +// } +// } +// None +// } + + + /// Checks if a given [`syn::Type`] is an `Option` type. + /// + /// This function examines a type to determine if it represents an `Option`. + /// It is useful for scenarios where type-specific behavior needs to be conditional + /// on whether the type is optional or not. + /// + /// # Example + /// + /// ```rust + /// let type_string = "Option< i32 >"; + /// let parsed_type : syn::Type = syn::parse_str( type_string ).expect( "Type should parse correctly" ); + /// assert!( macro_tools::typ::is_optional( &parsed_type ) ); + /// ``` + /// + + pub fn is_optional( ty : &syn::Type ) -> bool + { + typ::type_rightmost( ty ) == Some( "Option".to_string() ) + } + + /// Extracts the first generic parameter from a given `syn::Type` if any exists. + /// + /// This function is designed to analyze a type and retrieve its first generic parameter. + /// It is particularly useful when working with complex types in macro expansions and needs + /// to extract specific type information for further processing. + /// +/// + /// # Example + /// ```rust + /// let type_string = "Result< Option< i32 >, Error >"; + /// let parsed_type : syn::Type = syn::parse_str( type_string ).expect( "Type should parse correctly" ); + /// let first_param = macro_tools::typ::parameter_first( &parsed_type ).expect( "Should have at least one parameter" ); + /// // Option< i32 > + /// ``` + + pub fn parameter_first( ty : &syn::Type ) -> Result< &syn::Type > + { + typ::type_parameters( ty, 0 ..= 0 ) + .first() + .copied() + .ok_or_else( || syn_err!( ty, "Expects at least one parameter here:\n {}", qt!{ #ty } ) ) + } + } #[ doc( inline ) ] @@ -108,7 +170,9 @@ pub mod protected { type_rightmost, type_parameters, - // xxx : rename + // all_type_parameters, + is_optional, + parameter_first, }; } diff --git a/module/core/macro_tools/tests/inc/attr.rs b/module/core/macro_tools/tests/inc/attr.rs new file mode 100644 index 0000000000..6bba6e98fc --- /dev/null +++ b/module/core/macro_tools/tests/inc/attr.rs @@ -0,0 +1,53 @@ + +use super::*; + +// + +#[ test ] +fn parse() +{ + + let attr : syn::Attribute = syn::parse_quote!( #[ default( 31 ) ] ); + tree_print!( attr ); + + let attr : syn::Attribute = syn::parse_quote!( #[ default[ 31 ] ] ); + tree_print!( attr ); + + let attr : syn::Attribute = syn::parse_quote!( #[ former( default = 31 ) ] ); + // tree_print!( attr ); + let got = equation( &attr ).unwrap(); + a_id!( code_to_str!( got ), "default = 31".to_string() ); + a_id!( got.left, syn::parse_quote!( default ) ); + a_id!( got.op, syn::token::Eq::default() ); + a_id!( code_to_str!( got.right ), "31".to_string() ); + +} + +#[ test ] +fn is_standard_standard() +{ + // Test a selection of attributes known to be standard + assert!( is_standard( "cfg" ), "Expected 'cfg' to be a standard attribute." ); + assert!( is_standard( "derive" ), "Expected 'derive' to be a standard attribute." ); + assert!( is_standard( "inline" ), "Expected 'inline' to be a standard attribute." ); + assert!( is_standard( "test" ), "Expected 'test' to be a standard attribute." ); + assert!( is_standard( "doc" ), "Expected 'doc' to be a standard attribute." ); +} + +#[ test ] +fn is_standard_non_standard() +{ + // Test some made-up attributes that should not be standard + assert!( !is_standard( "custom_attr" ), "Expected 'custom_attr' to not be a standard attribute." ); + assert!( !is_standard( "my_attribute" ), "Expected 'my_attribute' to not be a standard attribute." ); + assert!( !is_standard( "special_feature" ), "Expected 'special_feature' to not be a standard attribute." ); +} + +#[ test ] +fn is_standard_edge_cases() +{ + // Test edge cases like empty strings or unusual input + assert!( !is_standard( "" ), "Expected empty string to not be a standard attribute." ); + assert!( !is_standard( " " ), "Expected a single space to not be a standard attribute." ); + assert!( !is_standard( "cfg_attr_extra" ), "Expected 'cfg_attr_extra' to not be a standard attribute." ); +} diff --git a/module/core/macro_tools/tests/inc/attr_test.rs b/module/core/macro_tools/tests/inc/attr_test.rs deleted file mode 100644 index 942289e7b3..0000000000 --- a/module/core/macro_tools/tests/inc/attr_test.rs +++ /dev/null @@ -1,24 +0,0 @@ - -use super::*; - -// - -#[ test ] -fn basic() -{ - - let attr : syn::Attribute = syn::parse_quote!( #[ default( 31 ) ] ); - tree_print!( attr ); - - let attr : syn::Attribute = syn::parse_quote!( #[ default[ 31 ] ] ); - tree_print!( attr ); - - let attr : syn::Attribute = syn::parse_quote!( #[ former( default = 31 ) ] ); - // tree_print!( attr ); - let got = equation( &attr ).unwrap(); - a_id!( code_to_str!( got ), "default = 31".to_string() ); - a_id!( got.left, syn::parse_quote!( default ) ); - a_id!( got.op, syn::token::Eq::default() ); - a_id!( code_to_str!( got.right ), "31".to_string() ); - -} diff --git a/module/core/macro_tools/tests/inc/basic_test.rs b/module/core/macro_tools/tests/inc/basic.rs similarity index 100% rename from module/core/macro_tools/tests/inc/basic_test.rs rename to module/core/macro_tools/tests/inc/basic.rs diff --git a/module/core/macro_tools/tests/inc/derive.rs b/module/core/macro_tools/tests/inc/derive.rs new file mode 100644 index 0000000000..b6983e34d5 --- /dev/null +++ b/module/core/macro_tools/tests/inc/derive.rs @@ -0,0 +1,72 @@ + +use super::*; + +// + +#[test] +fn named_fields_with_named_fields() +{ + use syn::{parse_quote, punctuated::Punctuated, Field, token::Comma}; + use the_module::derive; + + let ast: syn::DeriveInput = parse_quote! + { + struct Test + { + a : i32, + b : String, + } + }; + + let result = derive::named_fields( &ast ).expect( "Expected successful extraction of named fields" ); + + let mut expected_fields = Punctuated::new(); + let field_a : Field = parse_quote! { a: i32 }; + let field_b : Field = parse_quote! { b: String }; + expected_fields.push_value( field_a); + expected_fields.push_punct( Comma::default() ); + expected_fields.push_value( field_b ); + expected_fields.push_punct( Comma::default() ); + + a_id!( format!( "{:?}", result ), format!( "{:?}", expected_fields ), "Fields did not match expected output" ); +} + +// + +#[ test ] +fn named_fields_with_tuple_struct() +{ + use syn::{ parse_quote }; + use the_module::derive::named_fields; + + let ast : syn::DeriveInput = parse_quote! + { + struct Test( i32, String ); + }; + + let result = named_fields( &ast ); + + assert!( result.is_err(), "Expected an error for tuple struct, but extraction was successful" ); +} + +// + +#[ test ] +fn named_fields_with_enum() +{ + use syn::{ parse_quote }; + use the_module::derive::named_fields; + + let ast : syn::DeriveInput = parse_quote! + { + enum Test + { + Variant1, + Variant2, + } + }; + + let result = named_fields( &ast ); + + assert!( result.is_err(), "Expected an error for enum, but extraction was successful" ); +} diff --git a/module/core/macro_tools/tests/inc/generic_args.rs b/module/core/macro_tools/tests/inc/generic_args.rs new file mode 100644 index 0000000000..8076737930 --- /dev/null +++ b/module/core/macro_tools/tests/inc/generic_args.rs @@ -0,0 +1,356 @@ + +use super::*; + +// + +#[ test ] +fn assumptions() +{ + + // let code : syn::ItemStruct = syn::parse_quote! + // { + // pub struct Struct1Former + // < + // Definition = Struct1FormerDefinition< (), Struct1, former::ReturnPreformed >, + // > + // {} + // }; + // tree_print!( code ); + + // let mut a : syn::Generics = parse_quote! + // { + // < 'a, T > + // }; + // let mut b : syn::IntoGenericArgs = parse_quote! + // { + // < (), Struct1, former::ReturnPreformed > + // }; + // let got = generic_params::generic_args::merge( &a.into(), &b.into() ); + // // let got = definition_extra_generics; + + // let mut _got : syn::Generics = parse_quote! + // { + // < Struct1, former::ReturnPreformed > + // }; + + // let mut _got : syn::Generics = parse_quote! + // { + // < (), Struct1, former::ReturnPreformed > + // }; + +} + +// + +#[ test ] +fn into_generic_args_empty_generics() +{ + use syn::{ Generics, AngleBracketedGenericArguments, token }; + use macro_tools::IntoGenericArgs; + use proc_macro2::Span; + + let generics = Generics::default(); + let got = generics.into_generic_args(); + let exp = AngleBracketedGenericArguments + { + colon2_token: None, + lt_token: token::Lt::default(), + args: syn::punctuated::Punctuated::new(), + gt_token: token::Gt::default(), + }; + a_id!( exp, got, "Failed into_generic_args_empty_generics: exp {:?}, got {:?}", exp, got ); +} + +// +#[ test ] +fn into_generic_args_single_type_parameter() +{ + use syn:: + { + Generics, + AngleBracketedGenericArguments, + parse_quote + }; + use macro_tools::IntoGenericArgs; + + // Generate the generics with a single type parameter using parse_quote + let generics : Generics = parse_quote! + { + < T > + }; + + // Create the exp AngleBracketedGenericArguments using parse_quote + let exp : AngleBracketedGenericArguments = parse_quote! + { + < T > + }; + + let got = generics.into_generic_args(); + a_id!( exp, got, "Failed into_generic_args_single_type_parameter: exp {:?}, got {:?}", exp, got ); +} + +/// + +#[ test ] +fn into_generic_args_single_lifetime_parameter() +{ + use syn:: + { + Generics, + AngleBracketedGenericArguments, + GenericArgument, + parse_quote, + punctuated::Punctuated + }; + use macro_tools::IntoGenericArgs; + + // Generate the generics using parse_quote to include a lifetime parameter + let generics : Generics = parse_quote! + { + < 'a > + }; + + // Create the exp AngleBracketedGenericArguments using parse_quote + let exp : AngleBracketedGenericArguments = parse_quote! + { + < 'a > + }; + + // Use the implementation to generate the actual output + let got = generics.into_generic_args(); + + // Debug prints for better traceability in case of failure + println!( "Expected: {:?}", exp ); + println!( "Got: {:?}", got ); + + // Assert to check if the exp matches the got + a_id!( exp, got, "Failed into_generic_args_single_lifetime_parameter: exp {:?}, got {:?}", exp, got ); +} + +#[ test ] +fn into_generic_args_single_const_parameter() +{ + use syn:: + { + Generics, + AngleBracketedGenericArguments, + GenericArgument, + Expr, + ExprPath, + Ident, + token::{ self, Lt, Gt }, + punctuated::Punctuated + }; + use macro_tools::IntoGenericArgs; + + // Use parse_quote to create the generic parameters + let generics : Generics = parse_quote! + { + < const N: usize > + }; + + let got = generics.into_generic_args(); + + // Manually construct the exp value + let mut args = Punctuated::new(); + args.push_value( GenericArgument::Const( Expr::Path( ExprPath + { + attrs: vec![], + qself: None, + path: syn::Path::from( Ident::new( "N", proc_macro2::Span::call_site() )), + }))); + + let exp = AngleBracketedGenericArguments + { + colon2_token: None, + lt_token: Lt::default(), + args, + gt_token: Gt::default(), + }; + + // Debug prints for better traceability in case of failure + println!( "Expected: {:?}", exp ); + println!( "Got: {:?}", got ); + + a_id!( exp, got, "Failed into_generic_args_single_const_parameter: exp {:?}, got {:?}", exp, got ); +} + + +// + +#[ test ] +fn into_generic_args_mixed_parameters() +{ + use syn:: + { + Generics, + AngleBracketedGenericArguments, + GenericArgument, + Type, + TypePath, + Expr, + ExprPath, + Ident, + Lifetime, + token::{ self, Comma }, + punctuated::Punctuated, + parse_quote + }; + use macro_tools::IntoGenericArgs; + + // Generate the actual value using the implementation + let generics : Generics = parse_quote! + { + + }; + let got = generics.into_generic_args(); + + // Manually construct the exp value + let mut args = Punctuated::new(); + let t_type : GenericArgument = GenericArgument::Type( Type::Path( TypePath + { + qself: None, + path: Ident::new( "T", proc_macro2::Span::call_site() ).into(), + })); + args.push_value( t_type ); + args.push_punct( Comma::default() ); + + let a_lifetime = GenericArgument::Lifetime( Lifetime::new( "'a", proc_macro2::Span::call_site() )); + args.push_value( a_lifetime ); + args.push_punct( Comma::default() ); + + let n_const : GenericArgument = GenericArgument::Const( Expr::Path( ExprPath + { + attrs: vec![], + qself: None, + path: Ident::new( "N", proc_macro2::Span::call_site() ).into(), + })); + args.push_value( n_const ); + + let exp = AngleBracketedGenericArguments + { + colon2_token: None, + lt_token: token::Lt::default(), + args, + gt_token: token::Gt::default(), + }; + + // tree_print!( got ); + // tree_print!( exp ); + // a_id!(tree_diagnostics_str!( exp ), tree_diagnostics_str!( got ) ); + a_id!( exp, got, "Failed into_generic_args_mixed_parameters: exp {:?}, got {:?}", exp, got ); +} + +// = generic_args::merge + +#[ test ] +fn merge_empty_arguments() +{ + use syn::AngleBracketedGenericArguments; + use macro_tools::generic_args; + + let a : AngleBracketedGenericArguments = parse_quote! { <> }; + let b : AngleBracketedGenericArguments = parse_quote! { <> }; + let exp : AngleBracketedGenericArguments = parse_quote! { <> }; + + let got = generic_args::merge( &a, &b ); + a_id!( got, exp, "Merging two empty arguments should got in empty arguments" ); +} + +// + +#[ test ] +fn merge_one_empty_one_non_empty() +{ + use syn::AngleBracketedGenericArguments; + use macro_tools::generic_args; + + let a : AngleBracketedGenericArguments = parse_quote! { < T, U > }; + let b : AngleBracketedGenericArguments = parse_quote! { <> }; + let exp : AngleBracketedGenericArguments = parse_quote! { < T, U > }; + + let got = generic_args::merge( &a, &b ); + a_id!( got, exp, "Merging non-empty with empty should got in the non-empty" ); +} + +// + +#[ test ] +fn merge_duplicate_arguments() +{ + use syn::AngleBracketedGenericArguments; + use macro_tools::generic_args; + + let a : AngleBracketedGenericArguments = parse_quote! { < T > }; + let b : AngleBracketedGenericArguments = parse_quote! { < T > }; + let exp : AngleBracketedGenericArguments = parse_quote! { < T, T > }; + + let got = generic_args::merge( &a, &b ); + a_id!( got, exp, "Duplicates should be preserved in the output" ); +} + +// + +#[ test ] +fn merge_large_number_of_arguments() +{ + use syn::AngleBracketedGenericArguments; + use macro_tools::generic_args; + + let a : AngleBracketedGenericArguments = parse_quote! { }; + let b : AngleBracketedGenericArguments = parse_quote! { }; + let exp : AngleBracketedGenericArguments = parse_quote! { }; + + let got = generic_args::merge( &a, &b ); + a_id!( got, exp, "Merging large number of arguments should succeed without altering order or count" ); +} + +// + +#[ test ] +fn merge_complex_generic_constraints() +{ + use syn::AngleBracketedGenericArguments; + use macro_tools::generic_args; + + let a : AngleBracketedGenericArguments = parse_quote! { < T : Clone + Send, U: Default > }; + let b : AngleBracketedGenericArguments = parse_quote! { < V : core::fmt::Debug + Sync > }; + let exp : AngleBracketedGenericArguments = parse_quote! { < T: Clone + Send, U: Default, V: core::fmt::Debug + Sync > }; + + let got = generic_args::merge( &a, &b ); + a_id!( got, exp, "Complex constraints should be merged correctly" ); +} + +// + +#[ test ] +fn merge_different_orders_of_arguments() +{ + use syn::AngleBracketedGenericArguments; + use macro_tools::generic_args; + + let a : AngleBracketedGenericArguments = parse_quote! { < T, U > }; + let b : AngleBracketedGenericArguments = parse_quote! { < V, W > }; + let exp : AngleBracketedGenericArguments = parse_quote! { < T, U, V, W > }; + + let got = generic_args::merge( &a, &b ); + a_id!( got, exp, "Order of arguments should be preserved as per the inputs" ); +} + +// + +#[ test ] +fn merge_interaction_with_lifetimes_and_constants() +{ + use syn::AngleBracketedGenericArguments; + use macro_tools::generic_args; + + let a : AngleBracketedGenericArguments = parse_quote! { < 'a, M : T > }; + let b : AngleBracketedGenericArguments = parse_quote! { < 'b, N > }; + let exp : AngleBracketedGenericArguments = parse_quote! { <'a, 'b, M : T, N > }; + + let got = generic_args::merge( &a, &b ); + // a_id!(tree_diagnostics_str!( exp ), tree_diagnostics_str!( got ) ); + a_id!( got, exp, "Lifetimes and constants should be interleaved correctly" ); + +} diff --git a/module/core/macro_tools/tests/inc/generic_params.rs b/module/core/macro_tools/tests/inc/generic_params.rs new file mode 100644 index 0000000000..d9395b74a2 --- /dev/null +++ b/module/core/macro_tools/tests/inc/generic_params.rs @@ -0,0 +1,354 @@ + +use super::*; + +// + +#[ test ] +fn generics_with_where() +{ + + let got : the_module::GenericsWithWhere = parse_quote! + { + < 'a, T : Clone, U : Default, V : core::fmt::Debug > + where + Definition : former::FormerDefinition, + }; + let got = got.unwrap(); + + let mut exp : syn::Generics = parse_quote! + { + < 'a, T : Clone, U : Default, V : core::fmt::Debug > + }; + exp.where_clause = parse_quote! + { + where + Definition : former::FormerDefinition, + }; + + // a_id!( tree_print!( got ), tree_print!( exp ) ); + // code_print!( got ); + // code_print!( exp ); + // code_print!( got.where_clause ); + // code_print!( exp.where_clause ); + + assert_eq!( got.params, exp.params ); + assert_eq!( got.where_clause, exp.where_clause ); + assert_eq!( got, exp ); + +} + +// + +#[ test ] +fn merge_assumptions() +{ + use the_module::generic_params; + + let mut generics_a : syn::Generics = parse_quote!{ < T : Clone, U : Default > }; + generics_a.where_clause = parse_quote!{ where T : Default }; + let mut generics_b : syn::Generics = parse_quote!{ < V : core::fmt::Debug > }; + generics_b.where_clause = parse_quote!{ where V : Sized }; + let got = generic_params::merge( &generics_a, &generics_b ); + + let mut exp : syn::Generics = parse_quote! + { + < T : Clone, U : Default, V : core::fmt::Debug > + }; + exp.where_clause = parse_quote! + { + where + T : Default, + V : Sized + }; + + // a_id!( tree_print!( got ), tree_print!( exp ) ); + // code_print!( got ); + // code_print!( exp ); + // code_print!( got.where_clause ); + // code_print!( exp.where_clause ); + + assert_eq!( got.params, exp.params ); + assert_eq!( got.where_clause, exp.where_clause ); + assert_eq!( got, exp ); + +} + +// + +#[ test ] +fn merge_defaults() +{ + use the_module::generic_params; + + let mut generics_a : syn::Generics = parse_quote!{ < T : Clone, U : Default = Default1 > }; + generics_a.where_clause = parse_quote!{ where T : Default }; + let mut generics_b : syn::Generics = parse_quote!{ < V : core::fmt::Debug = Debug1 > }; + generics_b.where_clause = parse_quote!{ where V : Sized }; + let got = generic_params::merge( &generics_a, &generics_b ); + + let mut exp : syn::Generics = parse_quote! + { + < T : Clone, U : Default = Default1, V : core::fmt::Debug = Debug1 > + }; + exp.where_clause = parse_quote! + { + where + T : Default, + V : Sized + }; + + // a_id!( tree_print!( got ), tree_print!( exp ) ); + // code_print!( got ); + // code_print!( exp ); + // code_print!( got.where_clause ); + // code_print!( exp.where_clause ); + + assert_eq!( got.params, exp.params ); + assert_eq!( got.where_clause, exp.where_clause ); + assert_eq!( got, exp ); + +} + +// + +#[ test ] +fn names() +{ + + use macro_tools::syn::parse_quote; + + let generics : the_module::GenericsWithWhere = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > where T: core::fmt::Debug }; + let simplified_generics = macro_tools::generic_params::names( &generics.unwrap() ); + + assert_eq!( simplified_generics.params.len(), 4 ); // Contains T, U, 'a, and N + assert!( simplified_generics.where_clause.is_none() ); // Where clause is removed + +} + +// + +#[ test ] +fn decompose_empty_generics() +{ + let generics : syn::Generics = syn::parse_quote! {}; + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + assert!( impl_gen.is_empty(), "Impl generics should be empty" ); + assert!( ty_gen.is_empty(), "Type generics should be empty" ); + assert!( where_gen.is_empty(), "Where generics should be empty" ); +} + +#[ test ] +fn decompose_generics_without_where_clause() +{ + let generics : syn::Generics = syn::parse_quote! { < T, U > }; + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + assert_eq!( impl_gen.len(), 2, "Impl generics should have two parameters" ); + assert_eq!( ty_gen.len(), 2, "Type generics should have two parameters" ); + assert!( where_gen.is_empty(), "Where generics should be empty" ); + + let exp : syn::Generics = syn::parse_quote! { < T, U, > }; + a_id!( impl_gen, exp.params ); + let exp : syn::Generics = syn::parse_quote! { < T, U, > }; + a_id!( ty_gen, exp.params ); + // xxx : extend other tests + +} + +#[ test ] +fn decompose_generics_with_where_clause() +{ + use macro_tools::quote::ToTokens; + + let generics : the_module::GenericsWithWhere = syn::parse_quote! { < T, U > where T : Clone, U : Default }; + let generics = generics.unwrap(); + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + let impl_exp : syn::Generics = syn::parse_quote! { < T, U, > }; + let ty_exp : syn::Generics = syn::parse_quote! { < T, U, > }; + a_id!( impl_gen, impl_exp.params ); + a_id!( ty_gen, ty_exp.params ); + + assert_eq!( impl_gen.len(), 2, "Impl generics should have two parameters" ); + assert_eq!( ty_gen.len(), 2, "Type generics should have two parameters" ); + assert_eq!( where_gen.len(), 2, "Where generics should have two predicates" ); + + let where_clauses : Vec< _ > = where_gen.iter().collect(); + + // Properly match against the `syn::WherePredicate::Type` variant to extract `bounded_ty` + if let syn::WherePredicate::Type( pt ) = &where_clauses[0] + { + assert_eq!( pt.bounded_ty.to_token_stream().to_string(), "T", "The first where clause should be for T" ); + } + else + { + panic!( "First where clause is not a Type predicate as expected." ); + } + + if let syn::WherePredicate::Type( pt ) = &where_clauses[1] + { + assert_eq!( pt.bounded_ty.to_token_stream().to_string(), "U", "The second where clause should be for U" ); + } + else + { + panic!( "Second where clause is not a Type predicate as expected." ); + } +} + +#[ test ] +fn decompose_generics_with_only_where_clause() +{ + let generics : the_module::GenericsWithWhere = syn::parse_quote! { where T : Clone, U : Default }; + let generics = generics.unwrap(); + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + assert!( impl_gen.is_empty(), "Impl generics should be empty" ); + assert!( ty_gen.is_empty(), "Type generics should be empty" ); + assert_eq!( where_gen.len(), 2, "Where generics should have two predicates" ); + +} + +#[ test ] +fn decompose_generics_with_complex_constraints() +{ + use macro_tools::quote::ToTokens; + let generics : the_module::GenericsWithWhere = syn::parse_quote! { < T : Clone + Send, U : Default > where T: Send, U: Default }; + let generics = generics.unwrap(); + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + let impl_exp : syn::Generics = syn::parse_quote! { < T : Clone + Send, U : Default, > }; + let ty_exp : syn::Generics = syn::parse_quote! { < T, U, > }; + a_id!( impl_gen, impl_exp.params ); + a_id!( ty_gen, ty_exp.params ); + + assert_eq!( impl_gen.len(), 2, "Impl generics should reflect complex constraints" ); + assert_eq!( ty_gen.len(), 2, "Type generics should reflect complex constraints" ); + assert_eq!( where_gen.len(), 2, "Where generics should reflect complex constraints" ); + + let where_clauses : Vec<_> = where_gen.iter().collect(); + + // Properly matching against the WherePredicate::Type variant + if let syn::WherePredicate::Type( pt ) = &where_clauses[0] + { + assert_eq!( pt.bounded_ty.to_token_stream().to_string(), "T", "The first where clause should be for T" ); + } + else + { + panic!( "First where clause is not a Type predicate as expected." ); + } + + if let syn::WherePredicate::Type( pt ) = &where_clauses[1] + { + assert_eq!( pt.bounded_ty.to_token_stream().to_string(), "U", "The second where clause should be for U" ); + } + else + { + panic!( "Second where clause is not a Type predicate as expected." ); + } +} + +#[ test ] +fn decompose_generics_with_nested_generic_types() +{ + let generics : syn::Generics = syn::parse_quote! { < T : Iterator< Item = U >, U > }; + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + let impl_exp : syn::Generics = syn::parse_quote! { < T : Iterator< Item = U >, U, > }; + let ty_exp : syn::Generics = syn::parse_quote! { < T, U, > }; + a_id!( impl_gen, impl_exp.params ); + a_id!( ty_gen, ty_exp.params ); + + assert_eq!( impl_gen.len(), 2, "Impl generics should handle nested generics" ); + assert_eq!( ty_gen.len(), 2, "Type generics should handle nested generics" ); + assert!( where_gen.is_empty(), "Where generics should be empty for non-conditional types" ); +} + +#[ test ] +fn decompose_generics_with_lifetime_parameters_only() +{ + let generics : syn::Generics = syn::parse_quote! { < 'a, 'b > }; + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + let impl_exp : syn::Generics = syn::parse_quote! { < 'a, 'b, > }; + let ty_exp : syn::Generics = syn::parse_quote! { < 'a, 'b, > }; + a_id!( impl_gen, impl_exp.params ); + a_id!( ty_gen, ty_exp.params ); + + assert_eq!( impl_gen.len(), 2, "Impl generics should contain only lifetimes" ); + assert_eq!( ty_gen.len(), 2, "Type generics should contain only lifetimes" ); + assert!( where_gen.is_empty(), "Where generics should be empty" ); +} + +#[ test ] +fn decompose_generics_with_constants_only() +{ + let generics : syn::Generics = syn::parse_quote! { < const N : usize, const M : usize > }; + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + let impl_exp : syn::Generics = syn::parse_quote! { < const N : usize, const M : usize, > }; + let ty_exp : syn::Generics = syn::parse_quote! { < N, M, > }; + a_id!( impl_gen, impl_exp.params ); + a_id!( ty_gen, ty_exp.params ); + + assert_eq!( impl_gen.len(), 2, "Impl generics should contain constants" ); + assert_eq!( ty_gen.len(), 2, "Type generics should contain constants" ); + assert!( where_gen.is_empty(), "Where generics should be empty" ); +} + +#[ test ] +fn decompose_generics_with_default_values() +{ + let generics : syn::Generics = syn::parse_quote! { < T = usize, U = i32 > }; + let ( impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + let impl_with_exp : syn::Generics = syn::parse_quote! { < T = usize, U = i32, > }; + let impl_exp : syn::Generics = syn::parse_quote! { < T, U, > }; + let ty_exp : syn::Generics = syn::parse_quote! { < T, U, > }; + a_id!( impl_with_def, impl_with_exp.params ); + a_id!( impl_gen, impl_exp.params ); + a_id!( ty_gen, ty_exp.params ); + + assert_eq!( impl_gen.len(), 2, "Impl generics should retain default types" ); + assert_eq!( ty_gen.len(), 2, "Type generics should retain default types" ); + assert!( where_gen.is_empty(), "Where generics should be empty" ); +} + +#[ test ] +fn decompose_mixed_generics_types() +{ + use macro_tools::quote::ToTokens; + let generics : the_module::GenericsWithWhere = syn::parse_quote! { < 'a, T, const N : usize, U : Trait1 > where T : Clone, U : Default }; + let generics = generics.unwrap(); + let ( _impl_with_def, impl_gen, ty_gen, where_gen ) = the_module::generic_params::decompose( &generics ); + + let impl_exp : syn::Generics = syn::parse_quote! { < 'a, T, const N : usize, U : Trait1, > }; + let ty_exp : syn::Generics = syn::parse_quote! { < 'a, T, N, U, > }; + a_id!( impl_gen, impl_exp.params ); + a_id!( ty_gen, ty_exp.params ); + + assert_eq!( impl_gen.len(), 4, "Impl generics should correctly interleave types" ); + assert_eq!( ty_gen.len(), 4, "Type generics should correctly interleave types" ); + assert_eq!( where_gen.len(), 2, "Where generics should include conditions for T and U" ); + + // Correctly handling the pattern matching for WherePredicate::Type + let where_clauses : Vec<_> = where_gen.iter().collect(); + if let syn::WherePredicate::Type( pt ) = &where_clauses[0] + { + assert_eq!( pt.bounded_ty.to_token_stream().to_string(), "T", "The first where clause should be for T" ); + } + else + { + panic!( "First where clause is not a Type predicate as expected." ); + } + + if let syn::WherePredicate::Type( pt ) = &where_clauses[1] + { + assert_eq!( pt.bounded_ty.to_token_stream().to_string(), "U", "The second where clause should be for U" ); + } + else + { + panic!( "Second where clause is not a Type predicate as expected." ); + } + +} diff --git a/module/core/macro_tools/tests/inc/generics_test.rs b/module/core/macro_tools/tests/inc/generics_test.rs deleted file mode 100644 index 84c5090d0c..0000000000 --- a/module/core/macro_tools/tests/inc/generics_test.rs +++ /dev/null @@ -1,90 +0,0 @@ - -use super::*; - -// - -#[ test ] -fn basic() -{ - - let mut generics_a : syn::Generics = parse_quote!{ < T : Clone, U : Default > }; - generics_a.where_clause = parse_quote!{ where T : Default }; - let mut generics_b : syn::Generics = parse_quote!{ < V : std::fmt::Debug > }; - generics_b.where_clause = parse_quote!{ where V : Sized }; - let got = generics::merge( &generics_a, &generics_b ); - - let mut exp : syn::Generics = parse_quote! - { - < T : Clone, U : Default, V : std::fmt::Debug > - }; - exp.where_clause = parse_quote! - { - where - T : Default, - V : Sized - }; - - // a_id!( tree_print!( got ), tree_print!( exp ) ); - // code_print!( got ); - // code_print!( exp ); - // code_print!( got.where_clause ); - // code_print!( exp.where_clause ); - - assert_eq!( got.params, exp.params ); - assert_eq!( got.where_clause, exp.where_clause ); - assert_eq!( got, exp ); - -} - -// - -#[ test ] -fn merge_defaults() -{ - - let mut generics_a : syn::Generics = parse_quote!{ < T : Clone, U : Default = Default1 > }; - generics_a.where_clause = parse_quote!{ where T : Default }; - let mut generics_b : syn::Generics = parse_quote!{ < V : std::fmt::Debug = Debug1 > }; - generics_b.where_clause = parse_quote!{ where V : Sized }; - let got = generics::merge( &generics_a, &generics_b ); - - let mut exp : syn::Generics = parse_quote! - { - < T : Clone, U : Default = Default1, V : std::fmt::Debug = Debug1 > - }; - exp.where_clause = parse_quote! - { - where - T : Default, - V : Sized - }; - - // a_id!( tree_print!( got ), tree_print!( exp ) ); - // code_print!( got ); - // code_print!( exp ); - // code_print!( got.where_clause ); - // code_print!( exp.where_clause ); - - assert_eq!( got.params, exp.params ); - assert_eq!( got.where_clause, exp.where_clause ); - assert_eq!( got, exp ); - -} - -// - -#[ test ] -fn params_names() -{ - - use macro_tools::syn::parse_quote; - - let mut generics : syn::Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > }; - generics.where_clause = parse_quote!{ where T: std::fmt::Debug }; - // let generics : Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > where T: std::fmt::Debug }; - let simplified_generics = macro_tools::generics::params_names( &generics ); - - assert_eq!( simplified_generics.params.len(), 4 ); // Contains T, U, 'a, and N - assert!( simplified_generics.where_clause.is_none() ); // Where clause is removed - -} diff --git a/module/core/macro_tools/tests/inc/item.rs b/module/core/macro_tools/tests/inc/item.rs new file mode 100644 index 0000000000..a9652f81cd --- /dev/null +++ b/module/core/macro_tools/tests/inc/item.rs @@ -0,0 +1,118 @@ + +use super::*; + +#[ test ] +fn ensure_comma_named_struct_with_multiple_fields() +{ + use syn::{ parse_quote, ItemStruct }; + + let input_struct : ItemStruct = parse_quote! + { + struct Example + { + field1 : i32, + field2 : String + } + }; + + let got = the_module::item::ensure_comma( &input_struct ); + // let exp = "struct Example { field1 : i32, field2 : String, }"; + let exp : syn::ItemStruct = parse_quote! { struct Example { field1 : i32, field2 : String, } }; + // let got = quote!( #got ).to_string(); + // assert_eq!( exp, got ); + a_id!( got, exp ); + +} + +#[ test ] +fn ensure_comma_named_struct_with_single_field() +{ + use syn::{ parse_quote, ItemStruct }; + + let input_struct : ItemStruct = parse_quote! + { + struct Example + { + field1 : i32 + } + }; + + let got = the_module::item::ensure_comma( &input_struct ); + let exp : ItemStruct = parse_quote! { struct Example { field1 : i32, } }; + assert_eq!( got, exp ); +} + +#[ test ] +fn ensure_comma_named_struct_with_no_fields() +{ + use syn::{ parse_quote, ItemStruct }; + + let input_struct : ItemStruct = parse_quote! + { + struct Example { } + }; + + let got = the_module::item::ensure_comma( &input_struct ); + let exp : ItemStruct = parse_quote! { struct Example { } }; + assert_eq!( got, exp ); +} + +#[ test ] +fn ensure_comma_unnamed_struct_with_multiple_fields() +{ + use syn::{ parse_quote, ItemStruct }; + + let input_struct : ItemStruct = parse_quote! + { + struct Example( i32, String ); + }; + + let got = the_module::item::ensure_comma( &input_struct ); + let exp : ItemStruct = parse_quote! { struct Example( i32, String, ); }; + assert_eq!( got, exp ); +} + +#[ test ] +fn ensure_comma_unnamed_struct_with_single_field() +{ + use syn::{ parse_quote, ItemStruct }; + + let input_struct : ItemStruct = parse_quote! + { + struct Example( i32 ); + }; + + let got = the_module::item::ensure_comma( &input_struct ); + let exp : ItemStruct = parse_quote! { struct Example( i32, ); }; + assert_eq!( got, exp ); +} + +#[ test ] +fn ensure_comma_unnamed_struct_with_no_fields() +{ + use syn::{ parse_quote, ItemStruct }; + + let input_struct : ItemStruct = parse_quote! + { + struct Example( ); + }; + + let got = the_module::item::ensure_comma( &input_struct ); + let exp : ItemStruct = parse_quote! { struct Example( ); }; + assert_eq!( got, exp ); +} + +#[ test ] +fn ensure_comma_unit_struct_with_no_fields() +{ + use syn::{ parse_quote, ItemStruct }; + + let input_struct : ItemStruct = parse_quote! + { + struct Example; + }; + + let got = the_module::item::ensure_comma( &input_struct ); + let exp : ItemStruct = parse_quote! { struct Example; }; + assert_eq!( got, exp ); +} diff --git a/module/core/macro_tools/tests/inc/mod.rs b/module/core/macro_tools/tests/inc/mod.rs index c4063e67eb..9bf5c92947 100644 --- a/module/core/macro_tools/tests/inc/mod.rs +++ b/module/core/macro_tools/tests/inc/mod.rs @@ -6,17 +6,23 @@ use test_tools::exposed::*; #[ allow( unused_imports ) ] #[ cfg( feature = "enabled" ) ] -use the_module::exposed::*; +#[ path = "." ] +mod if_enabled +{ -#[ cfg( feature = "enabled" ) ] -mod attr_test; -#[ cfg( feature = "enabled" ) ] -mod basic_test; -#[ cfg( feature = "enabled" ) ] -mod generics_test; -#[ cfg( feature = "enabled" ) ] -mod quantifier_test; -#[ cfg( feature = "enabled" ) ] -mod syntax_test; -#[ cfg( feature = "enabled" ) ] -mod tokens_test; + use super::*; + use the_module::exposed::*; + + mod attr; + mod basic; + mod derive; + mod generic_args; + mod generic_params; + mod item; + mod phantom; + mod quantifier; + mod syntax; + mod tokens; + mod typ; + +} diff --git a/module/core/macro_tools/tests/inc/phantom.rs b/module/core/macro_tools/tests/inc/phantom.rs new file mode 100644 index 0000000000..990a63d2e7 --- /dev/null +++ b/module/core/macro_tools/tests/inc/phantom.rs @@ -0,0 +1,298 @@ + +use super::*; + +#[ test ] +fn phantom_add_basic() +{ + + let item : syn::ItemStruct = syn::parse_quote! + { + pub struct Struct1< 'a, Context, Formed > + { + f1 : int32, + } + }; + + let exp : syn::ItemStruct = syn::parse_quote! + { + pub struct Struct1< 'a, Context, Formed > + { + f1 : int32, + _phantom : core::marker::PhantomData< ( &'a(), *const Context, *const Formed ) >, + } + }; + + let got = the_module::phantom::add_to_item( &item ); + // a_id!( tree_print!( got ), tree_print!( exp ) ); + a_id!( got, exp ); + +} + +// + +#[ test ] +fn phantom_add_no_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct {} }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct + { + } + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_type_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct< T, U > {} }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct< T, U > + { + _phantom : core::marker::PhantomData< ( *const T, *const U ) >, + } + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_lifetime_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct< 'a, 'b > {} }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct< 'a, 'b > + { + _phantom : core::marker::PhantomData< ( &'a (), &'b () ) >, + } + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_const_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct< const N : usize > {} }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct< const N : usize > + { + _phantom : core::marker::PhantomData< ( N, ) >, + } + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_mixed_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct< T, 'a, const N : usize > {} }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct< T, 'a, const N : usize > + { + _phantom : core::marker::PhantomData< ( *const T, &'a (), N ) >, + } + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_named_fields() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct { field1 : i32, field2 : f64 } }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct + { + field1 : i32, + field2 : f64, + } + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_unnamed_fields() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct( i32, f64 ); }; + let got = the_module::phantom::add_to_item( &input ); + let exp : syn::ItemStruct = parse_quote! { struct TestStruct( i32, f64, ); }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_unnamed_fields_with_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct< T, U >( T, U ); }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct< T, U > + ( + T, U, + core::marker::PhantomData< ( *const T, *const U ) >, + ); + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_unnamed_fields_lifetime_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct< 'a, 'b >( &'a i32, &'b f64 ); }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct< 'a, 'b > + ( + &'a i32, + &'b f64, + core::marker::PhantomData< ( &'a (), &'b () ) >, + ); + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +#[ test ] +fn phantom_add_unnamed_fields_const_generics() +{ + use syn::parse_quote; + use quote::ToTokens; + + let input : syn::ItemStruct = parse_quote! { struct TestStruct< const N : usize >( [ i32 ; N ] ); }; + let got = the_module::phantom::add_to_item( &input ); + + let exp : syn::ItemStruct = parse_quote! + { + struct TestStruct< const N : usize > + ( + [ i32 ; N ], + core::marker::PhantomData< ( N, ) >, + ); + }; + + assert_eq!( got.to_token_stream().to_string(), exp.to_token_stream().to_string() ); +} + +// + +// +#[ test ] +fn phantom_tuple_empty_generics() +{ + use syn::{ punctuated::Punctuated, GenericParam, token::Comma, parse_quote }; + use macro_tools::phantom::tuple; + + let input : Punctuated< GenericParam, Comma > = Punctuated::new(); + let result = tuple( &input ); + + let exp : syn::Type = parse_quote! { core::marker::PhantomData<()> }; + let got = result; + + assert_eq!( format!( "{:?}", exp ), format!( "{:?}", got ), "Expected empty PhantomData, got: {:?}", got ); +} + +// + +#[ test ] +fn phantom_tuple_only_type_parameters() +{ + use syn::{ parse_quote, punctuated::Punctuated, GenericParam, token::Comma }; + use macro_tools::phantom::tuple; + + let input : Punctuated< GenericParam, Comma > = parse_quote! { T, U }; + let result = tuple( &input ); + + let exp : syn::Type = parse_quote! { core::marker::PhantomData< ( *const T, *const U ) > }; + let got = result; + + assert_eq!( format!( "{:?}", exp ), format!( "{:?}", got ), "Expected PhantomData with type parameters, got: {:?}", got ); +} + +// + +#[ test ] +fn phantom_tuple_mixed_generics() +{ + use syn::{ parse_quote, punctuated::Punctuated, GenericParam, token::Comma }; + use macro_tools::phantom::tuple; + + let input : Punctuated< GenericParam, Comma > = parse_quote! { T, 'a, const N: usize }; + let result = tuple( &input ); + + let exp : syn::Type = parse_quote! { core::marker::PhantomData< ( *const T, &'a (), N ) > }; + let got = result; + + assert_eq!( format!( "{:?}", exp ), format!( "{:?}", got ), "Expected PhantomData with mixed generics, got: {:?}", got ); +} diff --git a/module/core/macro_tools/tests/inc/quantifier_test.rs b/module/core/macro_tools/tests/inc/quantifier.rs similarity index 100% rename from module/core/macro_tools/tests/inc/quantifier_test.rs rename to module/core/macro_tools/tests/inc/quantifier.rs diff --git a/module/core/macro_tools/tests/inc/syntax_test.rs b/module/core/macro_tools/tests/inc/syntax.rs similarity index 100% rename from module/core/macro_tools/tests/inc/syntax_test.rs rename to module/core/macro_tools/tests/inc/syntax.rs diff --git a/module/core/macro_tools/tests/inc/tokens_test.rs b/module/core/macro_tools/tests/inc/tokens.rs similarity index 100% rename from module/core/macro_tools/tests/inc/tokens_test.rs rename to module/core/macro_tools/tests/inc/tokens.rs diff --git a/module/core/macro_tools/tests/inc/typ.rs b/module/core/macro_tools/tests/inc/typ.rs new file mode 100644 index 0000000000..75bd10096d --- /dev/null +++ b/module/core/macro_tools/tests/inc/typ.rs @@ -0,0 +1,128 @@ + +use super::*; + +// + +#[ test ] +fn is_optional_with_option_type() +{ + use syn::parse_str; + use macro_tools::typ::is_optional; + + let type_string = "Option"; + let parsed_type : syn::Type = parse_str( type_string ).expect( "Type should parse correctly" ); + + assert!( is_optional( &parsed_type ), "Expected type to be recognized as an Option" ); +} + +#[ test ] +fn is_optional_with_non_option_type() +{ + use syn::parse_str; + use macro_tools::typ::is_optional; + + let type_string = "Vec"; + let parsed_type : syn::Type = parse_str( type_string ).expect( "Type should parse correctly" ); + + assert!( !is_optional( &parsed_type ), "Expected type not to be recognized as an Option" ); +} + +#[ test ] +fn is_optional_with_nested_option_type() +{ + use syn::parse_str; + use macro_tools::typ::is_optional; + + let type_string = "Option>"; + let parsed_type : syn::Type = parse_str( type_string ).expect( "Type should parse correctly" ); + + assert!( is_optional( &parsed_type ), "Expected nested Option type to be recognized as an Option" ); +} + +#[ test ] +fn is_optional_with_similar_name_type() +{ + use syn::parse_str; + use macro_tools::typ::is_optional; + + let type_string = "OptionalValue"; + let parsed_type : syn::Type = parse_str( type_string ).expect( "Type should parse correctly" ); + + assert!( !is_optional( &parsed_type ), "Expected type with similar name not to be recognized as an Option" ); +} + +#[ test ] +fn is_optional_with_empty_input() +{ + use syn::{ parse_str, Type }; + use macro_tools::typ::is_optional; + + let type_string = ""; + let parsed_type_result = parse_str::< Type >( type_string ); + + assert!( parsed_type_result.is_err(), "Expected parsing to fail for empty input" ); +} + +// xxx + +#[ test ] +fn parameter_first_with_multiple_generics() +{ + use syn::{ parse_str, Type }; + use macro_tools::typ::parameter_first; + + let type_string = "Result, Error>"; + let parsed_type : Type = parse_str( type_string ).expect( "Type should parse correctly" ); + + let first_param = parameter_first( &parsed_type ).expect( "Expected to extract the first generic parameter" ); + + let expected_type : Type = parse_str( "Option" ).expect( "Expected type to parse correctly" ); + assert_eq!( format!( "{:?}", expected_type ), format!( "{:?}", first_param ), "Extracted type does not match expected" ); +} + +#[ test ] +fn parameter_first_with_no_generics() +{ + use syn::{ parse_str, Type }; + use macro_tools::typ::parameter_first; + + let type_string = "i32"; + let parsed_type : Type = parse_str( type_string ).expect( "Type should parse correctly" ); + let got = parameter_first( &parsed_type ).expect( "Type should parse correctly" ); + + // tree_print!( got.as_ref().unwrap() ); + + let expected_type : Type = parse_str( "i32" ).expect( "Expected type to parse correctly" ); + assert_eq!( format!( "{:?}", expected_type ), format!( "{:?}", got ), "Extracted type does not match expected" ); + +} + +#[ test ] +fn parameter_first_with_single_generic() +{ + use syn::{ parse_str, Type }; + use macro_tools::typ::parameter_first; + + let type_string = "Vec< i32 >"; + let parsed_type : Type = parse_str( type_string ).expect( "Type should parse correctly" ); + + let first_param = parameter_first( &parsed_type ).expect( "Expected to extract the first generic parameter" ); + + let expected_type : Type = parse_str( "i32" ).expect( "Expected type to parse correctly" ); + assert_eq!( format!( "{:?}", expected_type ), format!( "{:?}", first_param ), "Extracted type does not match expected" ); +} + +#[ test ] +fn parameter_first_with_deeply_nested_generics() +{ + use syn::{ parse_str, Type }; + use macro_tools::typ::parameter_first; + + let type_string = "Vec< HashMap< String, Option< i32 > > >"; + let parsed_type : Type = parse_str( type_string ).expect( "Type should parse correctly" ); + + let first_param = parameter_first( &parsed_type ).expect( "Expected to extract the first generic parameter" ); + + let expected_type : Type = parse_str( "HashMap< String, Option< i32 > >" ).expect( "Expected type to parse correctly" ); + assert_eq!( format!( "{:?}", expected_type ), format!( "{:?}", first_param ), "Extracted type does not match expected" ); +} diff --git a/module/core/mem_tools/Cargo.toml b/module/core/mem_tools/Cargo.toml index 43d80a2e3a..08bc56fc1a 100644 --- a/module/core/mem_tools/Cargo.toml +++ b/module/core/mem_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mem_tools" -version = "0.5.0" +version = "0.6.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/mem_tools/Readme.md b/module/core/mem_tools/Readme.md index f4f61aa89d..65fa607810 100644 --- a/module/core/mem_tools/Readme.md +++ b/module/core/mem_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: mem_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/mem_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/mem_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmem_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20mem_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mem_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/mem_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/mem_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/mem_tools/examples/mem_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/mem_tools/examples/mem_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of tools to manipulate memory. diff --git a/module/core/mem_tools/examples/mem_tools_trivial_sample.rs b/module/core/mem_tools/examples/mem_tools_trivial.rs similarity index 100% rename from module/core/mem_tools/examples/mem_tools_trivial_sample.rs rename to module/core/mem_tools/examples/mem_tools_trivial.rs diff --git a/module/core/meta_tools/Cargo.toml b/module/core/meta_tools/Cargo.toml index c339f1ab02..1fc7ff399b 100644 --- a/module/core/meta_tools/Cargo.toml +++ b/module/core/meta_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "meta_tools" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/meta_tools/Readme.md b/module/core/meta_tools/Readme.md index 00d04fd5d8..0cae84d5ad 100644 --- a/module/core/meta_tools/Readme.md +++ b/module/core/meta_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: meta_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/meta_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/meta_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmeta_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20meta_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_meta_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/meta_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/meta_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/meta_tools/examples/meta_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/meta_tools/examples/meta_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose meta tools. diff --git a/module/core/meta_tools/examples/meta_tools_trivial_sample/src/main.rs b/module/core/meta_tools/examples/meta_tools_trivial.rs similarity index 50% rename from module/core/meta_tools/examples/meta_tools_trivial_sample/src/main.rs rename to module/core/meta_tools/examples/meta_tools_trivial.rs index fc6c89b369..75d17ddace 100644 --- a/module/core/meta_tools/examples/meta_tools_trivial_sample/src/main.rs +++ b/module/core/meta_tools/examples/meta_tools_trivial.rs @@ -1,3 +1,4 @@ +//! This example showcases the usage of the `hmap!` macro from the `meta_tools` crate to create a hashmap and compares it with a hashmap created using `std::collections::HashMap`. use meta_tools::*; fn main() diff --git a/module/core/meta_tools/examples/meta_tools_trivial_sample/Cargo.toml b/module/core/meta_tools/examples/meta_tools_trivial_sample/Cargo.toml deleted file mode 100644 index 044d6b6c9d..0000000000 --- a/module/core/meta_tools/examples/meta_tools_trivial_sample/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "meta_tools_trivial" -version = "0.0.0" -edition = "2021" -publish = false - -[dependencies] -meta_tools = { workspace = true } diff --git a/module/core/meta_tools/examples/meta_tools_trivial_sample/Readme.md b/module/core/meta_tools/examples/meta_tools_trivial_sample/Readme.md deleted file mode 100644 index 5550ce94d8..0000000000 --- a/module/core/meta_tools/examples/meta_tools_trivial_sample/Readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# Sample - -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=sample%2Frust%2Fmeta_tools_trivial,SAMPLE_FILE=.%2Fsrc%2Fmain.rs/https://github.com/Wandalen/wTools) -[![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/meta_tools) diff --git a/module/core/meta_tools/src/lib.rs b/module/core/meta_tools/src/lib.rs index 4511f3c3ca..cd49c9841e 100644 --- a/module/core/meta_tools/src/lib.rs +++ b/module/core/meta_tools/src/lib.rs @@ -2,11 +2,6 @@ #![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ] #![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ] #![ doc( html_root_url = "https://docs.rs/meta_tools/latest/meta_tools/" ) ] - -//! -//! Collection of general purpose meta tools. -//! - #![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "Readme.md" ) ) ] /// Namespace with dependencies. diff --git a/module/core/mod_interface/Cargo.toml b/module/core/mod_interface/Cargo.toml index 86db3f8446..57abc5e5ab 100644 --- a/module/core/mod_interface/Cargo.toml +++ b/module/core/mod_interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mod_interface" -version = "0.17.0" +version = "0.18.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/mod_interface/Readme.md b/module/core/mod_interface/Readme.md index a1487f97b5..1115dea469 100644 --- a/module/core/mod_interface/Readme.md +++ b/module/core/mod_interface/Readme.md @@ -2,8 +2,7 @@ # Module :: mod_interface - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml)[![docs.rs](https://img.shields.io/docsrs/mod_interface?color=e3e8f0&logo=docs.rs)](https://docs.rs/mod_interface)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmod_interface_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20mod_interface_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_push.yml) [![docs.rs](https://img.shields.io/docsrs/mod_interface?color=e3e8f0&logo=docs.rs)](https://docs.rs/mod_interface) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Protocol of modularity unifying interface of a module and introducing layers. diff --git a/module/core/mod_interface/examples/mod_interface_trivial/src/main.rs b/module/core/mod_interface/examples/mod_interface_trivial/src/main.rs index 0f443a368e..03b8905594 100644 --- a/module/core/mod_interface/examples/mod_interface_trivial/src/main.rs +++ b/module/core/mod_interface/examples/mod_interface_trivial/src/main.rs @@ -2,9 +2,11 @@ // +use mod_interface::mod_interface; + fn main() { - assert_eq!( prelude::inner_is(), inner::prelude::inner_is() ); + assert_eq!( prelude::inner_is(), prelude::inner_is() ); } // diff --git a/module/core/mod_interface/tests/inc/trybuild_test.rs b/module/core/mod_interface/tests/inc/trybuild_test.rs index ba2083ed62..5acc2a4f29 100644 --- a/module/core/mod_interface/tests/inc/trybuild_test.rs +++ b/module/core/mod_interface/tests/inc/trybuild_test.rs @@ -11,50 +11,51 @@ use super::*; #[ test ] fn trybuild_tests() { - // use test_tools::dependency::trybuild; - println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); - // let t = trybuild::TestCases::new(); - let t = test_tools::compiletime::TestCases::new(); - - let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); - - let exe_directory = current_exe_path.parent().expect( "No such file or directory" ); - fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > - { - start_path - .ancestors() - .find( |path| path.join( "Cargo.toml" ).exists() ) - } - - let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); - let current_dir = workspace_root.join( "module/core/mod_interface" ); - - // micro module - - t.pass( current_dir.join( "tests/inc/derive/micro_modules/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/micro_modules_two/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/micro_modules_two_joined/trybuild.rs" ) ); - - // layer - - t.pass( current_dir.join( "tests/inc/derive/layer/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/layer_have_layer/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use_two/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_cfg/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/layer_use_cfg/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/layer_have_mod_cfg/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/layer_use_macro/trybuild.rs" ) ); - - // use - - t.pass( current_dir.join( "tests/inc/derive/use_basic/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/use_layer/trybuild.rs" ) ); - t.pass( current_dir.join( "tests/inc/derive/use_as/trybuild.rs" ) ); - - // attr - - t.pass( current_dir.join( "tests/inc/derive/attr_debug/trybuild.rs" ) ); + // qqq : fix test : if run its test with --target-dir flag it's fall (for example : cargo test --target-dir C:\foo\bar ) + // // use test_tools::dependency::trybuild; + // println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); + // // let t = trybuild::TestCases::new(); + // let t = test_tools::compiletime::TestCases::new(); + // + // let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); + // + // let exe_directory = dbg!(current_exe_path.parent().expect("No such file or directory")); + // fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > + // { + // start_path + // .ancestors() + // .find( |path| path.join( "Cargo.toml" ).exists() ) + // } + // + // let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); + // let current_dir = workspace_root.join( "module/core/mod_interface" ); + // + // // micro module + // + // t.pass( current_dir.join( "tests/inc/derive/micro_modules/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/micro_modules_two/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/micro_modules_two_joined/trybuild.rs" ) ); + // + // // layer + // + // t.pass( current_dir.join( "tests/inc/derive/layer/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_separate_use_two/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/layer_have_layer_cfg/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/layer_use_cfg/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/layer_have_mod_cfg/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/layer_use_macro/trybuild.rs" ) ); + // + // // use + // + // t.pass( current_dir.join( "tests/inc/derive/use_basic/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/use_layer/trybuild.rs" ) ); + // t.pass( current_dir.join( "tests/inc/derive/use_as/trybuild.rs" ) ); + // + // // attr + // + // t.pass( current_dir.join( "tests/inc/derive/attr_debug/trybuild.rs" ) ); // } @@ -67,29 +68,30 @@ only_for_terminal_module! #[ test ] fn cta_trybuild_tests() { + // qqq : fix test : if run its test with --target-dir flag it's fall (for example : cargo test --target-dir C:\foo\bar ) // use test_tools::dependency::trybuild; - println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); - // let t = trybuild::TestCases::new(); - let t = test_tools::compiletime::TestCases::new(); - - let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); - - let exe_directory = current_exe_path.parent().expect( "No such file or directory" ); - fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > - { - start_path - .ancestors() - .find( |path| path.join( "Cargo.toml" ).exists() ) - } - - let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); - let current_dir = workspace_root.join( "module/core/mod_interface" ); - - t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_bad_vis/trybuild.rs" ) ); - t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_unknown_vis/trybuild.rs" ) ); - t.compile_fail( current_dir.join( "tests/inc/derive/layer_bad_vis/trybuild.rs" ) ); - t.compile_fail( current_dir.join( "tests/inc/derive/layer_unknown_vis/trybuild.rs" ) ); - t.compile_fail( current_dir.join( "tests/inc/derive/use_bad_vis/trybuild.rs" ) ); - t.compile_fail( current_dir.join( "tests/inc/derive/use_unknown_vis/trybuild.rs" ) ); + // println!( "current_dir : {:?}", std::env::current_dir().unwrap() ); + // // let t = trybuild::TestCases::new(); + // let t = test_tools::compiletime::TestCases::new(); + // + // let current_exe_path = std::env::current_exe().expect( "No such file or directory" ); + // + // let exe_directory = current_exe_path.parent().expect( "No such file or directory" ); + // fn find_workspace_root( start_path : &std::path::Path ) -> Option< &std::path::Path > + // { + // start_path + // .ancestors() + // .find( |path| path.join( "Cargo.toml" ).exists() ) + // } + // + // let workspace_root = find_workspace_root( exe_directory ).expect( "No such file or directory" ); + // let current_dir = workspace_root.join( "module/core/mod_interface" ); + // + // t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_bad_vis/trybuild.rs" ) ); + // t.compile_fail( current_dir.join( "tests/inc/derive/micro_modules_unknown_vis/trybuild.rs" ) ); + // t.compile_fail( current_dir.join( "tests/inc/derive/layer_bad_vis/trybuild.rs" ) ); + // t.compile_fail( current_dir.join( "tests/inc/derive/layer_unknown_vis/trybuild.rs" ) ); + // t.compile_fail( current_dir.join( "tests/inc/derive/use_bad_vis/trybuild.rs" ) ); + // t.compile_fail( current_dir.join( "tests/inc/derive/use_unknown_vis/trybuild.rs" ) ); } } diff --git a/module/core/mod_interface_meta/Cargo.toml b/module/core/mod_interface_meta/Cargo.toml index 550f3b9984..1ccb41735a 100644 --- a/module/core/mod_interface_meta/Cargo.toml +++ b/module/core/mod_interface_meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mod_interface_meta" -version = "0.17.0" +version = "0.18.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/mod_interface_meta/Readme.md b/module/core/mod_interface_meta/Readme.md index 9fe44bec29..d9b2a9bd8b 100644 --- a/module/core/mod_interface_meta/Readme.md +++ b/module/core/mod_interface_meta/Readme.md @@ -2,8 +2,7 @@ # Module :: mod_interface_meta - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml)[![docs.rs](https://img.shields.io/docsrs/mod_interface_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/mod_interface_meta)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fmod_interface_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20mod_interface_meta_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_mod_interface_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/mod_interface_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/mod_interface_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Protocol of modularity unifying interface of a module and introducing layers. diff --git a/module/core/mod_interface_meta/src/impls.rs b/module/core/mod_interface_meta/src/impls.rs index 1538fb42e4..0f4608e420 100644 --- a/module/core/mod_interface_meta/src/impls.rs +++ b/module/core/mod_interface_meta/src/impls.rs @@ -460,9 +460,15 @@ pub( crate ) mod private if has_debug { - diag::debug_report_print( "derive : mod_interface", original_input, &result ); + let about = format!( "derive : mod_interface" ); + diag::report_print( about, &original_input, &result ); } + // if has_debug + // { + // diag::report_print( "derive : mod_interface", original_input, &result ); + // } + Ok( result ) } diff --git a/module/core/mod_interface_meta/src/lib.rs b/module/core/mod_interface_meta/src/lib.rs index c8e6d54d54..622fcd83d6 100644 --- a/module/core/mod_interface_meta/src/lib.rs +++ b/module/core/mod_interface_meta/src/lib.rs @@ -27,6 +27,17 @@ // xxx : make use proper_path_tools::protected::path working +// xxx : put modular files into a namespace `file` maybe +// #[ cfg( feature = "enabled" ) ] +// #[ path = "." ] +// mod file +// { +// use super::*; +// pub mod tokens; +// pub mod typ; +// pub mod type_struct; +// } + mod impls; #[ allow( unused_imports ) ] use impls::exposed::*; diff --git a/module/core/process_tools/Readme.md b/module/core/process_tools/Readme.md index 3f1125368b..97f7c673ea 100644 --- a/module/core/process_tools/Readme.md +++ b/module/core/process_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: process_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/process_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/process_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fprocess_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20process_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_process_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/process_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/process_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of algorithms and structures to handle processes properly. diff --git a/module/core/process_tools/src/process.rs b/module/core/process_tools/src/process.rs index f41e3bf120..eac9643740 100644 --- a/module/core/process_tools/src/process.rs +++ b/module/core/process_tools/src/process.rs @@ -14,8 +14,7 @@ pub( crate ) mod private use duct::cmd; use error_tools:: { - err, - for_app::{ Error, Context }, + for_app::{ Error, Context, anyhow }, Result, }; use former::Former; @@ -198,7 +197,7 @@ pub( crate ) mod private } else { - report.error = Err( err!( "Process was finished with error code : {}", output.status ) ); + report.error = Err( anyhow!( "Process was finished with error code : {}", output.status ) ); Err( report ) } @@ -212,7 +211,7 @@ pub( crate ) mod private bin_path : PathBuf, current_path : PathBuf, args : Vec< OsString >, - #[ default( false ) ] + #[ former( default = false ) ] joining_streams : bool, env_variable : HashMap< String, String >, } @@ -299,9 +298,9 @@ pub( crate ) mod private } } } - impl std::fmt::Display for Report + impl core::fmt::Display for Report { - fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f : &mut Formatter< '_ > ) -> core::fmt::Result { // Trim prevents writing unnecessary whitespace or empty lines f.write_fmt( format_args!( "> {}\n", self.command ) )?; diff --git a/module/core/proper_path_tools/Cargo.toml b/module/core/proper_path_tools/Cargo.toml index a9ae0e8b24..4c2691e250 100644 --- a/module/core/proper_path_tools/Cargo.toml +++ b/module/core/proper_path_tools/Cargo.toml @@ -25,16 +25,18 @@ all-features = false [features] default = [ "enabled", "path_unique_folder_name" ] -full = [ "enabled", "path_unique_folder_name" ] +full = [ "enabled", "path_unique_folder_name", "derive_serde" ] no_std = [] use_alloc = [ "no_std" ] enabled = [ "mod_interface/enabled" ] path_unique_folder_name = [] +derive_serde = [ "serde" ] [dependencies] regex = { version = "1.10.3" } mod_interface = { workspace = true } +serde = { version = "1.0.197", optional = true, features = [ "derive" ] } [dev-dependencies] test_tools = { workspace = true } diff --git a/module/core/proper_path_tools/Readme.md b/module/core/proper_path_tools/Readme.md index b51a7fd283..d142018019 100644 --- a/module/core/proper_path_tools/Readme.md +++ b/module/core/proper_path_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: proper_path_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/proper_path_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proper_path_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fproper_path_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20proper_path_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_proper_path_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/proper_path_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/proper_path_tools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of algorithms and structures to handle paths properly. diff --git a/module/core/proper_path_tools/src/lib.rs b/module/core/proper_path_tools/src/lib.rs index f2f4415a09..d437f55081 100644 --- a/module/core/proper_path_tools/src/lib.rs +++ b/module/core/proper_path_tools/src/lib.rs @@ -7,6 +7,10 @@ #[ cfg( feature = "enabled" ) ] use mod_interface::mod_interface; +#[ cfg( feature="no_std" ) ] +#[ macro_use ] +extern crate alloc; + #[ cfg( feature = "enabled" ) ] mod_interface! { diff --git a/module/core/proper_path_tools/src/path.rs b/module/core/proper_path_tools/src/path.rs index 35560947f7..7820003f65 100644 --- a/module/core/proper_path_tools/src/path.rs +++ b/module/core/proper_path_tools/src/path.rs @@ -1,6 +1,10 @@ /// Internal namespace. + pub( crate ) mod private { + #[cfg(feature="no_std")] + extern crate std; + // use std:: // { // path::{ Component, Path, PathBuf }, @@ -138,11 +142,11 @@ pub( crate ) mod private pub fn normalize< P : AsRef< std::path::Path > >( path : P ) -> std::path::PathBuf { - - use std:: - { - path::{ Component, PathBuf }, - }; + use std::path::{ Component, PathBuf }; + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::vec::Vec; let mut components = Vec::new(); let mut starts_with_dot = false; @@ -207,6 +211,7 @@ pub( crate ) mod private /// This function does not touch fs. pub fn canonicalize( path : impl AsRef< std::path::Path > ) -> std::io::Result< std::path::PathBuf > { + #[ cfg( target_os = "windows" ) ] use std::path::PathBuf; // println!( "a" ); @@ -265,15 +270,16 @@ pub( crate ) mod private /// ``` #[ cfg( feature = "path_unique_folder_name" ) ] - pub fn unique_folder_name() -> Result< String, std::time::SystemTimeError > + pub fn unique_folder_name() -> std::result::Result< std::string::String, std::time::SystemTimeError > { - use std:: - { - time::{ SystemTime, UNIX_EPOCH }, - }; + use std::time::{ SystemTime, UNIX_EPOCH }; + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::string::String; // Thread-local static variable for a counter - thread_local! + std::thread_local! { static COUNTER : std::cell::Cell< usize > = std::cell::Cell::new( 0 ); } @@ -291,21 +297,613 @@ pub( crate ) mod private .as_nanos(); let pid = std::process::id(); - let tid : String = format!( "{:?}", std::thread::current().id() ) + let tid : String = std::format!( "{:?}", std::thread::current().id() ) .chars() .filter( | c | c.is_digit( 10 ) ) .collect(); - // dbg!( &tid ); - Ok( format!( "{}_{}_{}_{}", timestamp, pid, tid, count ) ) + Ok( std::format!( "{}_{}_{}_{}", timestamp, pid, tid, count ) ) + } + + /// Extracts multiple extensions from the given path. + /// + /// This function takes a path and returns a vector of strings representing the extensions of the file. + /// If the input path is empty or if it doesn't contain any extensions, it returns an empty vector. + /// + /// # Arguments + /// + /// * `path` - An object that can be converted into a Path reference, representing the file path. + /// + /// # Returns + /// + /// A vector of strings containing the extensions of the file, or an empty vector if the input path is empty or lacks extensions. + /// + /// # Examples + /// + /// ``` + /// use proper_path_tools::path::exts; + /// + /// let path = "/path/to/file.tar.gz"; + /// let extensions = exts( path ); + /// assert_eq!( extensions, vec![ "tar", "gz" ] ); + /// ``` + /// + /// ``` + /// use proper_path_tools::path::exts; + /// + /// let empty_path = ""; + /// let extensions = exts( empty_path ); + /// let expected : Vec< String > = vec![]; + /// assert_eq!( extensions, expected ); + /// ``` + /// + pub fn exts( path : impl AsRef< std::path::Path > ) -> std::vec::Vec< std::string::String > + { + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::string::ToString; + + if let Some( file_name ) = std::path::Path::new( path.as_ref() ).file_name() + { + if let Some( file_name_str ) = file_name.to_str() + { + let mut file_name_str = file_name_str.to_string(); + if file_name_str.starts_with( '.' ) + { + file_name_str.remove( 0 ); + } + if let Some( dot_index ) = file_name_str.find( '.' ) + { + + let extensions = &file_name_str[ dot_index + 1.. ]; + + return extensions.split( '.' ).map( | s | s.to_string() ).collect() + } + } + } + vec![] + } + + + + + + /// Extracts the parent directory and file stem (without extension) from the given path. + /// + /// This function takes a path and returns an Option containing the modified path without the extension. + /// If the input path is empty or if it doesn't contain a file stem, it returns None. + /// + /// # Arguments + /// + /// * `path` - An object that can be converted into a Path reference, representing the file path. + /// + /// # Returns + /// + /// An Option containing the modified path without the extension, or None if the input path is empty or lacks a file stem. + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// use proper_path_tools::path::without_ext; + /// + /// let path = "/path/to/file.txt"; + /// let modified_path = without_ext(path); + /// assert_eq!(modified_path, Some(PathBuf::from("/path/to/file"))); + /// ``` + /// + /// ``` + /// use std::path::PathBuf; + /// use proper_path_tools::path::without_ext; + /// + /// let empty_path = ""; + /// let modified_path = without_ext(empty_path); + /// assert_eq!(modified_path, None); + /// ``` + /// + pub fn without_ext( path : impl AsRef< std::path::Path > ) -> core::option::Option< std::path::PathBuf > + { + use std::path::{ Path, PathBuf }; + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::string::String; + + if path.as_ref().to_string_lossy().is_empty() + { + return None; + } + + let path_buf = Path::new( path.as_ref() ); + + let parent = match path_buf.parent() + { + Some( parent ) => parent, + None => return None, + }; + let file_stem = match path_buf.file_stem() + { + Some( name ) => + { + let ends = format!( "{}/", name.to_string_lossy() ); + if path.as_ref().to_string_lossy().ends_with( &ends ) + { + ends + } + else + { + String::from( name.to_string_lossy() ) + } + + } + None => return None, + }; + + let mut full_path = parent.to_path_buf(); + full_path.push( file_stem ); + + Some( PathBuf::from( full_path.to_string_lossy().replace( "\\", "/" ) ) ) + } + + /// Finds the common directory path among a collection of paths. + /// + /// Given an iterator of path strings, this function determines the common directory + /// path shared by all paths. If no common directory path exists, it returns `None`. + /// + /// # Arguments + /// + /// * `paths` - An iterator of path strings (`&str`). + /// + /// # Returns + /// + /// * `Option` - The common directory path shared by all paths, if it exists. + /// If no common directory path exists, returns `None`. + /// + /// # Examples + /// + /// ``` + /// use proper_path_tools::path::path_common; + /// + /// let paths = vec![ "/a/b/c", "/a/b/d", "/a/b/e" ]; + /// let common_path = path_common( paths.into_iter() ); + /// assert_eq!( common_path, Some( "/a/b/".to_string() ) ); + /// ``` + /// + pub fn path_common< 'a, I >( paths : I ) -> Option< std::string::String > + where + I : Iterator< Item = &'a str >, + { + use std::collections::HashMap; + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::{ string::{ String, ToString }, vec::Vec }; + + let orig_paths : Vec< String > = paths.map( | path | path.to_string() ).collect(); + + if orig_paths.is_empty() + { + return None; + } + + // Create a map to store directory frequencies + let mut dir_freqs : HashMap< String, usize > = HashMap::new(); + + let mut paths = orig_paths.clone(); + // Iterate over paths to count directory frequencies + for path in paths.iter_mut() + { + path_remove_dots( path ); + path_remove_double_dots( path ); + // Split path into directories + let dirs : Vec< &str > = path.split( '/' ).collect(); + + // Iterate over directories + for i in 0..dirs.len() + { + + // Construct directory path + let mut dir_path = dirs[ 0..i + 1 ].join( "/" ); + + + // Increment frequency count + *dir_freqs.entry( dir_path.clone() ).or_insert( 0 ) += 1; + + if i != dirs.len() - 1 && !dirs[ i + 1 ].is_empty() + { + dir_path.push( '/' ); + *dir_freqs.entry( dir_path ).or_insert( 0 ) += 1; + } + } + } + + // Find the directory with the highest frequency + let common_dir = dir_freqs + .into_iter() + .filter( | ( _, freq ) | *freq == paths.len() ) + .map( | ( dir, _ ) | dir ) + .max_by_key( | dir | dir.len() ) + .unwrap_or_default(); + + let mut result = common_dir.to_string(); + + if result.is_empty() + { + if orig_paths.iter().any( | path | path.starts_with( '/' ) ) + { + result.push( '/' ); + } + else if orig_paths.iter().any( | path | path.starts_with( ".." ) ) + { + result.push_str( ".." ); + } + else + { + result.push( '.' ); + } + + } + + Some( result ) + + + } + + /// Removes dot segments (".") from the given path string. + /// + /// Dot segments in a path represent the current directory and can be safely removed + /// without changing the meaning of the path. + /// + /// # Arguments + /// + /// * `path` - A mutable reference to a string representing the path to be cleaned. + /// + fn path_remove_dots( path : &mut std::string::String ) + { + let mut cleaned_parts = vec![]; + + for part in path.split( '/' ) + { + if part == "." + { + continue; + } + + cleaned_parts.push( part ); + + } + + *path = cleaned_parts.join( "/" ); + + } + + /// Removes dot-dot segments ("..") from the given path string. + /// + /// Dot-dot segments in a path represent the parent directory and can be safely resolved + /// to simplify the path. + /// + /// # Arguments + /// + /// * `path` - A mutable reference to a string representing the path to be cleaned. + /// + fn path_remove_double_dots( path : &mut std::string::String ) + { + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::vec::Vec; + + let mut cleaned_parts: Vec< &str > = Vec::new(); + let mut delete_empty_part = false; + + for part in path.split( '/' ) + { + if part == ".." + { + if let Some( pop ) = cleaned_parts.pop() + { + if pop.is_empty() + { + delete_empty_part = true; + } + + if pop == ".." + { + cleaned_parts.push(".."); + cleaned_parts.push(".."); + } + } + else + { + cleaned_parts.push( ".." ); + } + } + else + { + cleaned_parts.push( part ); + } + } + if delete_empty_part + { + *path = format!( "/{}", cleaned_parts.join( "/" ) ); + } + else + { + *path = cleaned_parts.join( "/" ); + } + + } + + + /// Rebase the file path relative to a new base path, optionally removing a common prefix. + /// + /// # Arguments + /// + /// * `file_path` - The original file path to rebase. + /// * `new_path` - The new base path to which the file path will be rebased. + /// * `old_path` - An optional common prefix to remove from the file path before rebasing. + /// + /// # Returns + /// + /// Returns the rebased file path if successful, or None if any error occurs. + /// + /// # Examples + /// + /// Rebase a file path to a new base path without removing any common prefix: + /// + /// ``` + /// use std::path::PathBuf; + /// + /// let file_path = "/home/user/documents/file.txt"; + /// let new_path = "/mnt/storage"; + /// let rebased_path = proper_path_tools::path::rebase( file_path, new_path, None ).unwrap(); + /// assert_eq!( rebased_path, PathBuf::from( "/mnt/storage/home/user/documents/file.txt" ) ); + /// ``` + /// + /// Rebase a file path to a new base path after removing a common prefix: + /// + /// ``` + /// use std::path::PathBuf; + /// + /// let file_path = "/home/user/documents/file.txt"; + /// let new_path = "/mnt/storage"; + /// let old_path = "/home/user"; + /// let rebased_path = proper_path_tools::path::rebase( file_path, new_path, Some( old_path ) ).unwrap(); + /// assert_eq!( rebased_path, PathBuf::from( "/mnt/storage/documents/file.txt" ) ); + /// ``` + /// + pub fn rebase< T : AsRef< std::path::Path > >( file_path : T, new_path : T, old_path : Option< T > ) -> Option< std::path::PathBuf > + { + use std::path::Path; + use std::path::PathBuf; + + let new_path = Path::new( new_path.as_ref() ); + let mut main_file_path = Path::new( file_path.as_ref() ); + + if old_path.is_some() + { + let common = path_common( vec![ file_path.as_ref().to_str().unwrap(), old_path.unwrap().as_ref().to_str().unwrap() ].into_iter() )?; + + main_file_path = match main_file_path.strip_prefix( common ) + { + Ok( rel ) => rel, + Err( _ ) => return None, + }; + } + + let mut rebased_path = PathBuf::new(); + rebased_path.push( new_path ); + rebased_path.push( main_file_path.strip_prefix( "/" ).unwrap_or( main_file_path ) ); + + Some( normalize( rebased_path ) ) + } + + + /// Computes the relative path from one path to another. + /// + /// This function takes two paths and returns a relative path from the `from` path to the `to` path. + /// If the paths have different roots, the function returns the `to` path. + /// + /// # Arguments + /// + /// * `from` - The starting path. + /// * `to` - The target path. + /// + /// # Returns + /// + /// A `std::path::PathBuf` representing the relative path from `from` to `to`. + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// + /// let from = "/a/b"; + /// let to = "/a/c/d"; + /// let relative_path = proper_path_tools::path::path_relative( from, to ); + /// assert_eq!( relative_path, PathBuf::from( "../c/d" ) ); + /// ``` + pub fn path_relative< T : AsRef< std::path::Path > >( from : T, to : T ) -> std::path::PathBuf + { + use std::path::PathBuf; + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::{ vec::Vec, string::ToString }; + + let mut from = from.as_ref().to_string_lossy().to_string(); + let mut to = to.as_ref().to_string_lossy().to_string(); + + from = from.replace( ':', "" ); + to = to.replace( ':', "" ); + + + if from == "./" + { + from.push_str( &to ); + return PathBuf::from( from ) + } + + if from == "." + { + return PathBuf::from( to ) + } + + path_remove_double_dots( &mut from ); + path_remove_double_dots( &mut to ); + path_remove_dots( &mut from ); + path_remove_dots( &mut to ); + + let mut from_parts: Vec< &str > = from.split( '/' ).collect(); + let mut to_parts: Vec< &str > = to.split( '/' ).collect(); + + + if from_parts.len() == 1 && from_parts[ 0 ].is_empty() + { + from_parts.pop(); + } + + if to_parts.len() == 1 && to_parts[ 0 ].is_empty() + { + to_parts.pop(); + } + + let mut common_prefix = 0; + for ( idx, ( f, t ) ) in from_parts.iter().zip( to_parts.iter() ).enumerate() + { + if f != t + { + break; + } + common_prefix = idx + 1; + } + + let mut result = Vec::new(); + + // Add ".." for each directory not in common + for i in common_prefix..from_parts.len() + { + if from_parts[ common_prefix ].is_empty() || + ( + i == from_parts.len() - 1 + && from_parts[ i ].is_empty() + && !to_parts.last().unwrap_or( &"" ).is_empty() + ) + { + continue; + } + + result.push( ".." ); + } + + // Add the remaining directories from 'to' + for part in to_parts.iter().skip( common_prefix ) + { + result.push( *part ); + } + + // Join the parts into a string + let mut relative_path = result.join( "/" ); + + + + // If the relative path is empty or the 'to' path is the same as the 'from' path, + // set the relative path to "." + if relative_path.is_empty() || from == to + { + relative_path = ".".to_string(); + } + + + if to.ends_with( '/' ) && !relative_path.ends_with( '/' ) && to != "/" + { + relative_path.push( '/' ); + } + + + if from.ends_with( '/' ) && to.starts_with( '/' ) && relative_path.starts_with( ".." ) && relative_path != ".." + { + relative_path.replace_range( ..2 , "." ); + } + + if from.ends_with( '/' ) && to.starts_with( '/' ) && relative_path == ".." + { + relative_path = "./..".to_string(); + } + + PathBuf::from( relative_path ) + } + + + + + /// Extracts the extension from the given path. + /// + /// This function takes a path and returns a string representing the extension of the file. + /// If the input path is empty or if it doesn't contain an extension, it returns an empty string. + /// + /// # Arguments + /// + /// * `path` - An object that can be converted into a Path reference, representing the file path. + /// + /// # Returns + /// + /// A string containing the extension of the file, or an empty string if the input path is empty or lacks an extension. + /// + /// # Examples + /// + /// ``` + /// use proper_path_tools::path::ext; + /// + /// let path = "/path/to/file.txt"; + /// let extension = ext( path ); + /// assert_eq!( extension, "txt" ); + /// ``` + /// + /// ``` + /// use proper_path_tools::path::ext; + /// + /// let empty_path = ""; + /// let extension = ext( empty_path ); + /// assert_eq!( extension, "" ); + /// ``` + /// + pub fn ext( path : impl AsRef< std::path::Path > ) -> std::string::String + { + use std::path::Path; + #[ cfg( feature = "no_std" ) ] + extern crate alloc; + #[ cfg( feature = "no_std" ) ] + use alloc::string::{ String, ToString }; + + if path.as_ref().to_string_lossy().is_empty() + { + return String::new(); + } + let path_buf = Path::new( path.as_ref() ); + match path_buf.extension() + { + Some( ext ) => + { + ext.to_string_lossy().to_string() + } + None => String::new(), + } } } + crate::mod_interface! { - + protected use ext; + protected use exts; + protected use path_relative; + protected use rebase; + protected use path_common; + protected use without_ext; protected use is_glob; protected use normalize; protected use canonicalize; diff --git a/module/core/proper_path_tools/src/path/absolute_path.rs b/module/core/proper_path_tools/src/path/absolute_path.rs index dc494eda53..115c71d37b 100644 --- a/module/core/proper_path_tools/src/path/absolute_path.rs +++ b/module/core/proper_path_tools/src/path/absolute_path.rs @@ -1,15 +1,21 @@ /// Internal namespace. pub( crate ) mod private { + #[cfg(feature="no_std")] + extern crate std; use crate::*; use std:: { + borrow::Cow, fmt, path::{ Path, PathBuf }, }; + #[ cfg( feature = "derive_serde" ) ] + use serde::{ Serialize, Deserialize }; /// Absolute path. - #[ derive( Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash ) ] + #[ cfg_attr( feature = "derive_serde", derive( Serialize, Deserialize ) ) ] + #[ derive( Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Hash ) ] pub struct AbsolutePath( PathBuf ); impl fmt::Display for AbsolutePath @@ -51,6 +57,15 @@ pub( crate ) mod private } } + impl From< AbsolutePath > for PathBuf + { + fn from( abs_path: AbsolutePath ) -> Self + { + abs_path.0 + } + } + + // impl TryFrom< Utf8PathBuf > for AbsolutePath // { // type Error = std::io::Error; @@ -96,6 +111,13 @@ pub( crate ) mod private { Self::try_from( self.0.join( path ) ).unwrap() } + + /// Converts a `AbsolutePath` to a `Cow` + pub fn to_string_lossy( &self ) -> Cow< '_, str > + { + self.0.to_string_lossy() + } + } } diff --git a/module/core/proper_path_tools/tests/inc/absolute_path.rs b/module/core/proper_path_tools/tests/inc/absolute_path.rs index e3a291542f..9ef2c83a5f 100644 --- a/module/core/proper_path_tools/tests/inc/absolute_path.rs +++ b/module/core/proper_path_tools/tests/inc/absolute_path.rs @@ -1,15 +1,106 @@ #[ allow( unused_imports ) ] use super::*; +use the_module::AbsolutePath; +use std::path::Path; +use std::path::PathBuf; #[ test ] fn basic() { - use the_module::AbsolutePath; - let path1 = "/some/absolute/path"; let got : AbsolutePath = path1.try_into().unwrap(); println!( "got : {}", &got ); println!( "path1 : {}", &path1 ); a_id!( &got.to_string(), path1 ); +} + +#[ test ] +fn test_to_string_lossy() +{ + let path : AbsolutePath = "/path/to/file.txt".try_into().unwrap(); + let result = path.to_string_lossy(); + assert_eq!( result, "/path/to/file.txt" ); +} +#[test] +fn test_to_string_lossy_hard() +{ + let abs_path : AbsolutePath = "/path/with/😀/unicode.txt".try_into().unwrap(); + let string_lossy = abs_path.to_string_lossy(); + assert_eq!( string_lossy, "/path/with/\u{1F600}/unicode.txt" ); +} + + +#[test] +fn test_try_from_pathbuf() +{ + + let path_buf = PathBuf::from( "/path/to/some/file.txt" ); + let abs_path : AbsolutePath = path_buf.try_into().unwrap(); + assert_eq!( abs_path.to_string_lossy(), "/path/to/some/file.txt" ); +} + +#[test] +fn test_try_from_path() +{ + let path = Path::new( "/path/to/some/file.txt" ); + let abs_path : AbsolutePath = path.try_into().unwrap(); + assert_eq!( abs_path.to_string_lossy(), "/path/to/some/file.txt" ); +} + +#[test] +fn test_parent() +{ + let abs_path : AbsolutePath = "/path/to/some/file.txt".try_into().unwrap(); + let parent_path = abs_path.parent().unwrap(); + assert_eq!( parent_path.to_string_lossy(), "/path/to/some" ); +} + +#[test] +fn test_join() +{ + let abs_path : AbsolutePath = "/path/to/some".try_into().unwrap(); + let joined_path = abs_path.join( "file.txt" ); + assert_eq!( joined_path.to_string_lossy(), "/path/to/some/file.txt" ); +} + +#[test] +fn test_relative_path_try_from_str() +{ + let rel_path_str = "src/main.rs"; + let rel_path = AbsolutePath::try_from( rel_path_str ).unwrap(); + assert_eq!( rel_path.to_string_lossy(), "src/main.rs" ); +} + +#[test] +fn test_relative_path_try_from_pathbuf() +{ + let rel_path_buf = PathBuf::from( "src/main.rs" ); + let rel_path = AbsolutePath::try_from( rel_path_buf.clone() ).unwrap(); + assert_eq!( rel_path.to_string_lossy(), "src/main.rs" ); } + +#[test] +fn test_relative_path_try_from_path() +{ + let rel_path = Path::new( "src/main.rs" ); + let rel_path_result = AbsolutePath::try_from( rel_path ); + assert!( rel_path_result.is_ok() ); + assert_eq!( rel_path_result.unwrap().to_string_lossy(), "src/main.rs" ); +} + +#[test] +fn test_relative_path_parent() +{ + let rel_path = AbsolutePath::try_from( "src/main.rs" ).unwrap(); + let parent_path = rel_path.parent().unwrap(); + assert_eq!( parent_path.to_string_lossy(), "src" ); +} + +#[test] +fn test_relative_path_join() +{ + let rel_path = AbsolutePath::try_from( "src" ).unwrap(); + let joined = rel_path.join( "main.rs" ); + assert_eq!( joined.to_string_lossy(), "src/main.rs" ); +} \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/inc/mod.rs b/module/core/proper_path_tools/tests/inc/mod.rs index cc74b4a975..4107e22677 100644 --- a/module/core/proper_path_tools/tests/inc/mod.rs +++ b/module/core/proper_path_tools/tests/inc/mod.rs @@ -4,6 +4,12 @@ use super::*; mod path_normalize; mod path_is_glob; mod absolute_path; +mod path_exts; +mod path_ext; +mod without_ext; +mod path_common; +mod rebase_path; +mod path_relative; #[ cfg( feature = "path_unique_folder_name" ) ] mod path_unique_folder_name; diff --git a/module/core/proper_path_tools/tests/inc/path_common.rs b/module/core/proper_path_tools/tests/inc/path_common.rs new file mode 100644 index 0000000000..b491d2106c --- /dev/null +++ b/module/core/proper_path_tools/tests/inc/path_common.rs @@ -0,0 +1,506 @@ +#[ allow( unused_imports ) ] +use super::*; + + +#[ test ] +fn test_with_empty_array() +{ + let paths : Vec< &str > = vec![]; + let got = the_module::path::path_common( paths.into_iter() ); + assert_eq!( got, None ); +} + +// absolute-absolute + +#[ test ] +fn test_absolute_absolute_have_common_dir() +{ + let got = the_module::path::path_common( vec![ "/a1/b2", "/a1/a" ].into_iter() ).unwrap(); + assert_eq!( got, "/a1/" ); +} + +#[ test ] +fn test_absolute_absolute_have_common_dir_2() +{ + let got = the_module::path::path_common( vec![ "/a1/b1/c", "/a1/b1/d", "/a1/b2" ].into_iter() ).unwrap(); + assert_eq!( got, "/a1/" ); +} + +#[ test ] +fn test_absolute_absolute_have_common_dir_and_part_of_name() +{ + let got = the_module::path::path_common( vec![ "/a1/b2", "/a1/b1" ].into_iter() ).unwrap(); + assert_eq!( got, "/a1/" ); +} + +#[ test ] +fn test_absolute_absolute_one_path_has_dots_identical_paths() +{ + let got = the_module::path::path_common( vec![ "/a1/x/../b1", "/a1/b1" ].into_iter() ).unwrap(); + assert_eq!( got, "/a1/b1" ); +} + +#[ test ] +fn test_absolute_absolute_more_than_one_dir_in_common_path() +{ + let got = the_module::path::path_common( vec![ "/a1/b1/c1", "/a1/b1/c" ].into_iter() ).unwrap(); + assert_eq!( got, "/a1/b1/" ); +} + +#[ test ] +fn test_absolute_absolute_one_path_have_dots_no_common_dirs() +{ + let got = the_module::path::path_common( vec![ "/a1/../../b1/c1", "/a1/b1/c1" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_absolute_dir_name_is_part_of_another_dir_name() +{ + let got = the_module::path::path_common( vec![ "/abcd", "/ab" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_absolute_dir_names_has_dots_have_common_path() +{ + let got = the_module::path::path_common( vec![ "/.a./.b./.c.", "/.a./.b./.c" ].into_iter() ).unwrap(); + assert_eq!( got, "/.a./.b./" ); +} + +#[ test ] +fn test_absolute_absolute_one_path_has_several_slashes_the_other_has_not_not_identical() +{ + let got = the_module::path::path_common( vec![ "//a//b//c", "/a/b" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_absolute_identical_paths_with_several_slashes() +{ + let got = the_module::path::path_common( vec![ "/a//b", "/a//b" ].into_iter() ).unwrap(); + assert_eq!( got, "/a//b" ); +} + +#[ test ] +fn test_absolute_absolute_identical_paths_with_several_slashes_2() +{ + let got = the_module::path::path_common( vec![ "/a//", "/a//" ].into_iter() ).unwrap(); + assert_eq!( got, "/a//" ); +} + +#[ test ] +fn test_absolute_absolute_one_path_has_here_token_dirs_identical_paths() +{ + let got = the_module::path::path_common( vec![ "/./a/./b/./c", "/a/b" ].into_iter() ).unwrap(); + assert_eq!( got, "/a/b" ); +} + +#[ test ] +fn test_absolute_absolute_different_case_in_path_name_not_identical() +{ + let got = the_module::path::path_common( vec![ "/A/b/c", "/a/b/c" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_absolute_one_path_is_root_directory_common_root_directory() +{ + let got = the_module::path::path_common( vec![ "/", "/x" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_absolute_different_paths_in_root_directory_common_root_directory() +{ + let got = the_module::path::path_common( vec![ "/a", "/x" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + + +// more than 2 path in arguments + +#[ test ] +fn test_absolute_absolute_more_than_2_path_in_arguments() +{ + let got = the_module::path::path_common( vec![ "/a/b/c", "/a/b/c", "/a/b/c", "/a/b/c" ].into_iter() ).unwrap(); + assert_eq!( got, "/a/b/c" ); +} + +#[ test ] +fn test_absolute_absolute_more_than_2_path_in_arguments_variant2() +{ + let got = the_module::path::path_common( vec![ "/a/b/c", "/a/b/c", "/a/b" ].into_iter() ).unwrap(); + assert_eq!( got, "/a/b" ); +} + +#[ test ] +fn test_absolute_absolute_more_than_2_path_in_arguments_variant3() +{ + let got = the_module::path::path_common( vec![ "/a/b/c", "/a/b/c", "/a/b1" ].into_iter() ).unwrap(); + assert_eq!( got, "/a/" ); +} + +#[ test ] +fn test_absolute_absolute_more_than_2_path_in_arguments_variant4() +{ + let got = the_module::path::path_common( vec![ "/a/b/c", "/a/b/c", "/a" ].into_iter() ).unwrap(); + assert_eq!( got, "/a" ); +} + +#[ test ] +fn test_absolute_absolute_more_than_2_path_in_arguments_variant5() +{ + let got = the_module::path::path_common( vec![ "/a/b/c", "/a/b/c", "/x" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_absolute_more_than_2_path_in_arguments_variant6() +{ + let got = the_module::path::path_common( vec![ "/a/b/c", "/a/b/c", "/" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + + + + + + + + + +// absolute-relative + +#[ test ] +fn test_absolute_relative_root_and_down_token() +{ + let got = the_module::path::path_common( vec![ "/", ".." ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_relative_root_and_here_token() +{ + let got = the_module::path::path_common( vec![ "/", "." ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_relative_root_and_some_relative_directory() +{ + let got = the_module::path::path_common( vec![ "/", "x" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_relative_root_and_double_down_token_in_path() +{ + let got = the_module::path::path_common( vec![ "/", "../.." ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_relative_root_with_here_token_and_down_token() +{ + let got = the_module::path::path_common( vec![ "/.", ".." ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_relative_root_with_here_token_and_here_token() +{ + let got = the_module::path::path_common( vec![ "/.", "." ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_relative_root_with_here_token_and_some_relative_directory() +{ + let got = the_module::path::path_common( vec![ "/.", "x" ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + +#[ test ] +fn test_absolute_relative_root_with_here_token_and_double_down_token_in_path() +{ + let got = the_module::path::path_common( vec![ "/.", "../.." ].into_iter() ).unwrap(); + assert_eq!( got, "/" ); +} + + + + + + + +// relative - relative +#[ test ] +fn test_relative_relative_common_dir() +{ + let got = the_module::path::path_common( vec![ "a1/b2", "a1/a" ].into_iter() ).unwrap(); + assert_eq!( got, "a1/" ); +} + +#[ test ] +fn test_relative_relative_common_dir_and_part_of_dir_names() +{ + let got = the_module::path::path_common( vec![ "a1/b2", "a1/b1" ].into_iter() ).unwrap(); + assert_eq!( got, "a1/" ); +} + +#[ test ] +fn test_relative_relative_one_path_with_down_token_dir_identical_paths() +{ + let got = the_module::path::path_common( vec![ "a1/x/../b1", "a1/b1" ].into_iter() ).unwrap(); + assert_eq!( got, "a1/b1" ); +} + +#[ test ] +fn test_relative_relative_paths_begins_with_here_token_directory_dots_identical_paths() +{ + let got = the_module::path::path_common( vec![ "./a1/x/../b1", "./a1/b1" ].into_iter() ).unwrap(); + assert_eq!( got, "a1/b1" ); +} + +#[ test ] +fn test_relative_relative_one_path_begins_with_here_token_dir_another_down_token() +{ + let got = the_module::path::path_common( vec![ "./a1/x/../b1", "../a1/b1" ].into_iter() ).unwrap(); + assert_eq!( got, ".." ); +} + +#[ test ] +fn test_relative_relative_here_token_and_down_token() +{ + let got = the_module::path::path_common( vec![ ".", ".." ].into_iter() ).unwrap(); + assert_eq!( got, ".." ); +} + +#[ test ] +fn test_relative_relative_different_paths_start_with_here_token_dir() +{ + let got = the_module::path::path_common( vec![ "./b/c", "./x" ].into_iter() ).unwrap(); + assert_eq!( got, "." ); +} + + + + +//combinations of paths with dots + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots() +{ + let got = the_module::path::path_common( vec![ "./././a", "./a/b" ].into_iter() ).unwrap(); + assert_eq!( got, "a" ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant2() +{ + let got = the_module::path::path_common( vec![ "./a/./b", "./a/b" ].into_iter() ).unwrap(); + assert_eq!( got, "a/b" ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant3() +{ + let got = the_module::path::path_common( vec![ "./a/./b", "./a/c/../b" ].into_iter() ).unwrap(); + assert_eq!( got, "a/b" ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant4() +{ + let got = the_module::path::path_common( vec![ "../b/c", "./x" ].into_iter() ).unwrap(); + assert_eq!( got, ".." ); +} + + + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant9() +{ + let got = the_module::path::path_common( vec![ "../../..", "./../../.." ].into_iter() ).unwrap(); + assert_eq!( got, "../../.." ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant10() +{ + let got = the_module::path::path_common( vec![ "./../../..", "./../../.." ].into_iter() ).unwrap(); + assert_eq!( got, "../../.." ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant11() +{ + let got = the_module::path::path_common( vec![ "../../..", "../../.." ].into_iter() ).unwrap(); + assert_eq!( got, "../../.." ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant12() +{ + let got = the_module::path::path_common( vec![ "../b", "../b" ].into_iter() ).unwrap(); + assert_eq!( got, "../b" ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant13() +{ + let got = the_module::path::path_common( vec![ "../b", "./../b" ].into_iter() ).unwrap(); + assert_eq!( got, "../b" ); +} + + +// several relative paths + +#[ test ] +fn test_relative_relative_several_relative_paths() +{ + let got = the_module::path::path_common( vec![ "a/b/c", "a/b/c", "a/b/c" ].into_iter() ).unwrap(); + assert_eq!( got, "a/b/c" ); +} + +#[ test ] +fn test_relative_relative_several_relative_paths_variant2() +{ + let got = the_module::path::path_common( vec![ "a/b/c", "a/b/c", "a/b" ].into_iter() ).unwrap(); + assert_eq!( got, "a/b" ); +} + +#[ test ] +fn test_relative_relative_several_relative_paths_variant3() +{ + let got = the_module::path::path_common( vec![ "a/b/c", "a/b/c", "a/b1" ].into_iter() ).unwrap(); + assert_eq!( got, "a/" ); +} + +#[ test ] +fn test_relative_relative_several_relative_paths_variant4() +{ + let got = the_module::path::path_common( vec![ "a/b/c", "a/b/c", "." ].into_iter() ).unwrap(); + assert_eq!( got, "." ); +} + +#[ test ] +fn test_relative_relative_several_relative_paths_variant5() +{ + let got = the_module::path::path_common( vec![ "a/b/c", "a/b/c", "x" ].into_iter() ).unwrap(); + assert_eq!( got, "." ); +} + +#[ test ] +fn test_relative_relative_several_relative_paths_variant6() +{ + let got = the_module::path::path_common( vec![ "a/b/c", "a/b/c", "./" ].into_iter() ).unwrap(); + assert_eq!( got, "." ); +} + +#[ test ] +fn test_relative_relative_several_relative_paths_variant7() +{ + let got = the_module::path::path_common( vec![ "../a/b/c", "a/../b/c", "a/b/../c" ].into_iter() ).unwrap(); + assert_eq!( got, ".." ); +} + + + +#[ test ] +fn test_relative_relative_dot_and_double_up_and_down_tokens() +{ + let got = the_module::path::path_common( vec![ ".", "./", ".." ].into_iter() ).unwrap(); + assert_eq!( got, ".." ); +} + + + +/* + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant5() +{ + let got = the_module::path::path_common( vec![ "../../b/c", "../b" ].into_iter() ).unwrap(); + assert_eq!( got, "../.." ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant6() +{ + let got = the_module::path::path_common( vec![ "../../b/c", "../../../x" ].into_iter() ).unwrap(); + assert_eq!( got, "../../.." ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant7() +{ + let got = the_module::path::path_common( vec![ "../../b/c/../../x", "../../../x" ].into_iter() ).unwrap(); + assert_eq!( got, "../../.." ); +} + +#[ test ] +fn test_relative_relative_combinations_of_paths_with_dots_variant8() +{ + let got = the_module::path::path_common( vec![ "./../../b/c/../../x", "./../../../x" ].into_iter() ).unwrap(); + assert_eq!( got, "../../.." ); +} + + +#[ test ] +fn test_relative_relative_dot_and_double_up_and_down_tokens_variant2() +{ + let got = the_module::path::path_common( vec![ ".", "./../..", ".." ].into_iter() ).unwrap(); + assert_eq!( got, "../.." ); +} + +#[ test ] +fn test_relative_relative_several_relative_paths_variant8() +{ + let got = the_module::path::path_common( vec![ "./a/b/c", "../../a/b/c", "../../../a/b" ].into_iter() ).unwrap(); + assert_eq!( got, "../../.." ); +} + + + + + + + + + +#[ test ] +#[ should_panic ] +fn test_first_path_is_absolute_another_is_dots() +{ + the_module::path::path_common( vec![ "/a", ".."]); +} + +#[ test ] +#[ should_panic ] +fn test_first_path_is_dots_and_absolute_path() +{ + the_module::path::path_common( vec![ "..", "../../b/c", "/a"]); +} + +#[ test ] +#[ should_panic ] +fn test_first_path_is_dots_and_absolute_path_variant2() +{ + the_module::path::path_common( vec![ "../..", "../../b/c", "/a"]); +} + +#[ test ] +#[ should_panic ] +fn test_unknown_path() +{ + the_module::path::path_common( vec![ "/a", "x"]); +} + +#[ test ] +#[ should_panic ] +fn test_unknown_path_variant2() +{ + the_module::path::path_common( vec![ "x", "/a/b/c", "/a"]); +} */ \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/inc/path_ext.rs b/module/core/proper_path_tools/tests/inc/path_ext.rs new file mode 100644 index 0000000000..63de0bfcca --- /dev/null +++ b/module/core/proper_path_tools/tests/inc/path_ext.rs @@ -0,0 +1,44 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn empty_path() +{ + let path = ""; + assert_eq!( the_module::path::ext( path ), "" ); +} + +#[ test ] +fn txt_extension() +{ + let path = "some.txt"; + assert_eq!( the_module::path::ext( path ), "txt" ); +} + +#[ test ] +fn path_with_non_empty_dir_name() +{ + let path = "/foo/bar/baz.asdf"; + assert_eq!( the_module::path::ext( path ), "asdf" ); +} + +#[ test ] +fn hidden_file() +{ + let path = "/foo/bar/.baz"; + assert_eq!( the_module::path::ext( path ), "" ); +} + +#[ test ] +fn several_extension() +{ + let path = "/foo.coffee.md"; + assert_eq!( the_module::path::ext( path ), "md" ); +} + +#[ test ] +fn file_without_extension() +{ + let path = "/foo/bar/baz"; + assert_eq!( the_module::path::ext( path ), "" ); +} \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/inc/path_exts.rs b/module/core/proper_path_tools/tests/inc/path_exts.rs new file mode 100644 index 0000000000..2e96a55341 --- /dev/null +++ b/module/core/proper_path_tools/tests/inc/path_exts.rs @@ -0,0 +1,50 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn empty_path() +{ + let path = ""; + let expected : Vec< String > = vec![]; + assert_eq!( the_module::path::exts( path ), expected ); +} + +#[ test ] +fn txt_extension() +{ + let path = "some.txt"; + let expected : Vec< String > = vec![ "txt".to_string() ]; + assert_eq!( the_module::path::exts( path ), expected ); +} + +#[ test ] +fn path_with_non_empty_dir_name() +{ + let path = "/foo/bar/baz.asdf"; + let expected : Vec< String > = vec![ "asdf".to_string() ]; + assert_eq!( the_module::path::exts( path ), expected ); +} + +#[ test ] +fn hidden_file() +{ + let path = "/foo/bar/.baz"; + let expected : Vec< String > = vec![]; + assert_eq!( the_module::path::exts( path ), expected ); +} + +#[ test ] +fn several_extension() +{ + let path = "/foo.coffee.md"; + let expected : Vec< String > = vec![ "coffee".to_string(), "md".to_string() ]; + assert_eq!( the_module::path::exts( path ), expected ); +} + +#[ test ] +fn hidden_file_extension() +{ + let path = "/foo/bar/.baz.txt"; + let expected : Vec< String > = vec![ "txt".to_string() ]; + assert_eq!( the_module::path::exts( path ), expected ); +} \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/inc/path_relative.rs b/module/core/proper_path_tools/tests/inc/path_relative.rs new file mode 100644 index 0000000000..7c9f6bfbed --- /dev/null +++ b/module/core/proper_path_tools/tests/inc/path_relative.rs @@ -0,0 +1,403 @@ +#[ allow( unused_imports ) ] +use super::*; +use std::path::PathBuf; + + +// absolute path relative + +#[ test ] +fn test_absolute_a_minus_b() +{ + let from = "/a"; + let to = "/b"; + let expected = "../b"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( PathBuf::from( expected ) ) ); +} + +#[ test ] +fn test_absolute_root_minus_b() +{ + let from = "/"; + let to = "/b"; + let expected = "b"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_same_path() +{ + let from = "/aa/bb/cc"; + let to = "/aa/bb/cc"; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_same_path_with_trail() +{ + let from = "/aa/bb/cc"; + let to = "/aa/bb/cc/"; + let expected = "./"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_two_trailed_absolute_paths() +{ + let from = "/a/b/"; + let to = "/a/b/"; + let expected = "./"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_two_absolute_paths_with_trail() +{ + let from = "/a/b"; + let to = "/a/b/"; + let expected = "./"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_two_absolute_paths() +{ + let from = "/a/b/"; + let to = "/a/b"; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_same_path_trail_to_not() +{ + let from = "/aa/bb/cc/"; + let to = "/aa/bb/cc"; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_a_to_double_slash_b() +{ + let from = "/a"; + let to = "//b"; + let expected = "..//b"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + + +#[ test ] +fn test_absolute_relative_to_nested() +{ + let from = "/foo/bar/baz/asdf/quux"; + let to = "/foo/bar/baz/asdf/quux/new1"; + let expected = "new1"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_out_of_relative_dir() +{ + let from = "/abc"; + let to = "/a/b/z"; + let expected = "../a/b/z"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_relative_root() +{ + let from = "/"; + let to = "/a/b/z"; + let expected = "a/b/z"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + + +#[ test ] +fn test_long_not_direct() +{ + let from = "/a/b/xx/yy/zz"; + let to = "/a/b/files/x/y/z.txt"; + let expected = "../../../files/x/y/z.txt"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_relative_to_parent_directory() +{ + let from = "/aa/bb/cc"; + let to = "/aa/bb"; + let expected = ".."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_relative_to_parent_directory_file_trailed() +{ + let from = "/aa/bb/cc"; + let to = "/aa/bb/"; + let expected = "../"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_relative_root_to_root() +{ + let from = "/"; + let to = "/"; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_windows_disks() +{ + let from = "d:/"; + let to = "c:/x/y"; + let expected = "../c/x/y"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + + +#[ test ] +fn test_absolute_relative_to_parent_directory_both_trailed() +{ + let from = "/aa/bb/cc/"; + let to = "/aa/bb/"; + let expected = "./../"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + + +#[ test ] +fn test_absolute_a_with_trail_to_double_slash_b_with_trail() +{ + let from = "/a/"; + let to = "//b/"; + let expected = "./..//b/"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_4_down() +{ + let from = "/aa//bb/cc/"; + let to = "//xx/yy/zz/"; + let expected = "./../../../..//xx/yy/zz/"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_same_length_both_trailed() +{ + let from = "/aa//bb/cc/"; + let to = "//xx/yy/zz/"; + let expected = "./../../../..//xx/yy/zz/"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_absolute_relative_to_parent_directory_base_trailed() +{ + let from = "/aa/bb/cc/"; + let to = "/aa/bb"; + let expected = "./.."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + + + + + +// relative_path_relative + +#[ test ] +fn test_relative_dot_to_dot() +{ + let from = "."; + let to = "."; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_to_b() +{ + let from = "a"; + let to = "b"; + let expected = "../b"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_b_to_b_c() +{ + let from = "a/b"; + let to = "b/c"; + let expected = "../../b/c"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_b_to_a_b_c() +{ + let from = "a/b"; + let to = "a/b/c"; + let expected = "c"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_b_c_to_a_b() +{ + let from = "a/b/c"; + let to = "a/b"; + let expected = ".."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_b_c_d_to_a_b_d_c() +{ + let from = "a/b/c/d"; + let to = "a/b/d/c"; + let expected = "../../d/c"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_to_dot_dot_a() +{ + let from = "a"; + let to = "../a"; + let expected = "../../a"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_slash_slash_b_to_a_slash_slash_c() +{ + let from = "a//b"; + let to = "a//c"; + let expected = "../c"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_dot_slash_b_to_a_dot_slash_c() +{ + let from = "a/./b"; + let to = "a/./c"; + let expected = "../c"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_dot_dot_slash_b_to_b() +{ + let from = "a/../b"; + let to = "b"; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_b_to_b_dot_dot_slash_b() +{ + let from = "b"; + let to = "b/../b"; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_to_dot_dot() +{ + let from = "."; + let to = ".."; + let expected = ".."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_to_dot_dot_dot() +{ + let from = "."; + let to = "../.."; + let expected = "../.."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_dot_to_dot_dot() +{ + let from = ".."; + let to = "../.."; + let expected = ".."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_dot_to_dot_dot_dot() +{ + let from = ".."; + let to = ".."; + let expected = "."; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_dot_a_b_to_dot_dot_c_d() +{ + let from = "../a/b"; + let to = "../c/d"; + let expected = "../../c/d"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_to_b() +{ + let from = "."; + let to = "b"; + let expected = "b"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_slash_to_b() +{ + let from = "./"; + let to = "b"; + let expected = "./b"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_to_b_slash() +{ + let from = "."; + let to = "b/"; + let expected = "b/"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_dot_slash_to_b_slash() +{ + let from = "./"; + let to = "b/"; + let expected = "./b/"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} + +#[ test ] +fn test_relative_a_dot_dot_to_b_dot_dot() +{ + let from = "a/../b/.."; + let to = "b"; + let expected = "b"; + assert_eq!( the_module::path::path_relative( from, to ), PathBuf::from( expected ) ); +} \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/inc/rebase_path.rs b/module/core/proper_path_tools/tests/inc/rebase_path.rs new file mode 100644 index 0000000000..7c8db4350c --- /dev/null +++ b/module/core/proper_path_tools/tests/inc/rebase_path.rs @@ -0,0 +1,57 @@ +#[ allow( unused_imports ) ] +use super::*; +use std::path::PathBuf; + +#[ test ] +fn test_rebase_without_old_path() +{ + let file_path = "/home/user/documents/file.txt"; + let new_path = "/mnt/storage"; + let rebased_path = the_module::path::rebase( &file_path, &new_path, None ).unwrap(); + assert_eq! + ( + rebased_path, + PathBuf::from( "/mnt/storage/home/user/documents/file.txt" ) + ); +} + +#[ test ] +fn test_rebase_with_old_path() +{ + let file_path = "/home/user/documents/file.txt"; + let new_path = "/mnt/storage"; + let old_path = "/home/user"; + let rebased_path = the_module::path::rebase( &file_path, &new_path, Some( &old_path ) ).unwrap(); + assert_eq! + ( + rebased_path, + PathBuf::from( "/mnt/storage/documents/file.txt" ) + ); +} + +#[ test ] +fn test_rebase_invalid_old_path() +{ + let file_path = "/home/user/documents/file.txt"; + let new_path = "/mnt/storage"; + let old_path = "/tmp"; + let rebased_path = the_module::path::rebase( &file_path, &new_path, Some( &old_path ) ).unwrap(); + assert_eq! + ( + rebased_path, + PathBuf::from( "/mnt/storage/home/user/documents/file.txt" ) + ); +} + +#[ test ] +fn test_rebase_non_ascii_paths() +{ + let file_path = "/home/пользователь/documents/файл.txt"; // Non-ASCII file path + let new_path = "/mnt/存储"; // Non-ASCII new base path + let rebased_path = the_module::path::rebase( &file_path, &new_path, None ).unwrap(); + assert_eq! + ( + rebased_path, + PathBuf::from( "/mnt/存储/home/пользователь/documents/файл.txt" ) + ); +} \ No newline at end of file diff --git a/module/core/proper_path_tools/tests/inc/without_ext.rs b/module/core/proper_path_tools/tests/inc/without_ext.rs new file mode 100644 index 0000000000..fa1c5bf11e --- /dev/null +++ b/module/core/proper_path_tools/tests/inc/without_ext.rs @@ -0,0 +1,114 @@ +#[ allow( unused_imports ) ] +use super::*; + +#[ test ] +fn empty_path() +{ + let path = ""; + let expected = None; + assert_eq!( the_module::path::without_ext( path ), expected ); +} + +#[ test ] +fn txt_extension() +{ + let path = "some.txt"; + let expected = "some"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn path_with_non_empty_dir_name() +{ + let path = "/foo/bar/baz.asdf"; + let expected = "/foo/bar/baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn hidden_file() +{ + let path = "/foo/bar/.baz"; + let expected = "/foo/bar/.baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn file_with_composite_file_name() +{ + let path = "/foo.coffee.md"; + let expected = "/foo.coffee"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn path_without_extension() +{ + let path = "/foo/bar/baz"; + let expected = "/foo/bar/baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_1() +{ + let path = "./foo/.baz"; + let expected = "./foo/.baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_2() +{ + let path = "./.baz"; + let expected = "./.baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_3() +{ + let path = ".baz.txt"; + let expected = ".baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_4() +{ + let path = "./baz.txt"; + let expected = "./baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_5() +{ + let path = "./foo/baz.txt"; + let expected = "./foo/baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_6() +{ + let path = "./foo/"; + let expected = "./foo/"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_7() +{ + let path = "baz"; + let expected = "baz"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} + +#[ test ] +fn relative_path_8() +{ + let path = "baz.a.b"; + let expected = "baz.a"; + assert_eq!( the_module::path::without_ext( path ).unwrap().to_string_lossy(), expected ); +} \ No newline at end of file diff --git a/module/core/reflect_tools/Readme.md b/module/core/reflect_tools/Readme.md index b0a2c24b6a..82980784f3 100644 --- a/module/core/reflect_tools/Readme.md +++ b/module/core/reflect_tools/Readme.md @@ -2,8 +2,7 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/reflect_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/reflect_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Freflect_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20reflect_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/reflect_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/reflect_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/reflect_tools/examples/reflect_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/reflect_tools/examples/reflect_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) ### Basic use-case diff --git a/module/core/reflect_tools/src/reflect/axiomatic.rs b/module/core/reflect_tools/src/reflect/axiomatic.rs index 4dd0cfb454..df63730d59 100644 --- a/module/core/reflect_tools/src/reflect/axiomatic.rs +++ b/module/core/reflect_tools/src/reflect/axiomatic.rs @@ -349,36 +349,36 @@ pub( crate ) mod private } } - impl< T > std::fmt::Debug for EntityDescriptor< T > + impl< T > core::fmt::Debug for EntityDescriptor< T > where T : Instance + 'static, EntityDescriptor< T > : Entity, { - fn fmt( &self, f: &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f: &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result { f .write_str( &format!( "{}#{:?}", self.type_name(), self.type_id() ) ) } } - impl< T > std::fmt::Debug for CollectionDescriptor< T > + impl< T > core::fmt::Debug for CollectionDescriptor< T > where T : Instance + 'static, CollectionDescriptor< T > : Entity, { - fn fmt( &self, f: &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f: &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result { f .write_str( &format!( "{}#{:?}", self.type_name(), self.type_id() ) ) } } - impl< T > std::fmt::Debug for KeyedCollectionDescriptor< T > + impl< T > core::fmt::Debug for KeyedCollectionDescriptor< T > where T : Instance + 'static, KeyedCollectionDescriptor< T > : Entity, { - fn fmt( &self, f: &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f: &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result { f .write_str( &format!( "{}#{:?}", self.type_name(), self.type_id() ) ) @@ -415,9 +415,9 @@ pub( crate ) mod private } } - impl std::fmt::Debug for KeyVal + impl core::fmt::Debug for KeyVal { - fn fmt( &self, f: &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + fn fmt( &self, f: &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result { f .debug_struct( "KeyVal" ) diff --git a/module/core/reflect_tools/tests/inc/reflect_hashset_test.rs b/module/core/reflect_tools/tests/inc/reflect_hashset_test.rs index 05cb597ea5..07ce5911c1 100644 --- a/module/core/reflect_tools/tests/inc/reflect_hashset_test.rs +++ b/module/core/reflect_tools/tests/inc/reflect_hashset_test.rs @@ -18,7 +18,7 @@ fn reflect_hashset_test() a_id!( reflect( &set ).is_container(), true ); a_id!( reflect( &set ).len(), 3 ); - a_id!( reflect( &set ).type_name(), "std::collections::hash::set::HashSet< i32 >" ); + a_id!( reflect( &set ).type_name(), "std::collections::hash::set::HashSet" ); a_id!( reflect( &set ).type_id(), core::any::TypeId::of::< HashSet< i32 > >() ); let expected = vec! diff --git a/module/core/reflect_tools/tests/inc/reflect_vec_test.rs b/module/core/reflect_tools/tests/inc/reflect_vec_test.rs index ef9b668d1a..48ac9a3477 100644 --- a/module/core/reflect_tools/tests/inc/reflect_vec_test.rs +++ b/module/core/reflect_tools/tests/inc/reflect_vec_test.rs @@ -17,7 +17,7 @@ fn reflect_vec_test() a_id!( reflect( &vec ).is_container(), true ); a_id!( reflect( &vec ).len(), 3 ); - a_id!( reflect( &vec ).type_name(), "alloc::vec::Vec< i32 >" ); + a_id!( reflect( &vec ).type_name(), "alloc::vec::Vec" ); a_id!( reflect( &vec ).type_id(), core::any::TypeId::of::< Vec< i32 > >() ); let expected = vec! diff --git a/module/core/reflect_tools/tests/tests.rs b/module/core/reflect_tools/tests/tests.rs index 8bbcd66a9d..b8bdcf97f4 100644 --- a/module/core/reflect_tools/tests/tests.rs +++ b/module/core/reflect_tools/tests/tests.rs @@ -4,5 +4,6 @@ use reflect_tools as the_module; #[ allow( unused_imports ) ] use test_tools::exposed::*; +#[ cfg( feature = "enabled" ) ] mod inc; diff --git a/module/core/reflect_tools_meta/Readme.md b/module/core/reflect_tools_meta/Readme.md index c9d3a600ba..9d7ee04fc9 100644 --- a/module/core/reflect_tools_meta/Readme.md +++ b/module/core/reflect_tools_meta/Readme.md @@ -1,8 +1,7 @@ # Module :: reflect_tools_meta - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml)[![docs.rs](https://img.shields.io/docsrs/reflect_tools_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/reflect_tools_meta)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Freflect_tools_meta_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20reflect_tools_meta_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_reflect_tools_meta_push.yml) [![docs.rs](https://img.shields.io/docsrs/reflect_tools_meta?color=e3e8f0&logo=docs.rs)](https://docs.rs/reflect_tools_meta) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of mechanisms for reflection. Its meta module. Don't use directly. diff --git a/module/core/strs_tools/Readme.md b/module/core/strs_tools/Readme.md index f162e59059..8bedfb898d 100644 --- a/module/core/strs_tools/Readme.md +++ b/module/core/strs_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: strs_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/strs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/strs_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fstrs_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20strs_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_strs_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/strs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/strs_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/strs_tools/examples/strs_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/strs_tools/examples/strs_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools to manipulate strings. diff --git a/module/core/strs_tools/examples/str_toolst_trivial_sample.rs b/module/core/strs_tools/examples/strs_tools_trivial.rs similarity index 100% rename from module/core/strs_tools/examples/str_toolst_trivial_sample.rs rename to module/core/strs_tools/examples/strs_tools_trivial.rs diff --git a/module/core/strs_tools/src/string/isolate.rs b/module/core/strs_tools/src/string/isolate.rs index abe3ddc13b..78d23f6658 100644 --- a/module/core/strs_tools/src/string/isolate.rs +++ b/module/core/strs_tools/src/string/isolate.rs @@ -11,17 +11,17 @@ pub( crate ) mod private #[ perform( fn isolate( &self ) -> ( &'a str, Option<&'a str>, &'a str ) ) ] pub struct IsolateOptions<'a> { - #[ default( "" ) ] + #[ former( default = "" ) ] src : &'a str, - #[ default( " " ) ] + #[ former( default = " " ) ] delimeter : &'a str, - #[ default( true ) ] + #[ former( default = true ) ] quote : bool, - #[ default( true ) ] + #[ former( default = true ) ] left : bool, - #[ default( 1 ) ] + #[ former( default = 1 ) ] times : u8, /* rrr : Dmytro : former do not form u16, u32, u64, usize, replace after fix */ - #[ default( true ) ] + #[ former( default = true ) ] none : bool, } diff --git a/module/core/strs_tools/src/string/parse_request.rs b/module/core/strs_tools/src/string/parse_request.rs index f972a50852..e3c68de8f9 100644 --- a/module/core/strs_tools/src/string/parse_request.rs +++ b/module/core/strs_tools/src/string/parse_request.rs @@ -160,21 +160,21 @@ pub( crate ) mod private #[ perform( fn parse( mut self ) -> Request< 'a > ) ] pub struct ParseOptions< 'a > { - #[ default( "" ) ] + #[ former( default = "" ) ] src : &'a str, - #[ default( ":" ) ] + #[ former( default = ":" ) ] key_val_delimeter : &'a str, - #[ default( ";" ) ] + #[ former( default = ";" ) ] commands_delimeter : &'a str, - #[ default( true ) ] + #[ former( default = true ) ] quoting : bool, - #[ default( true ) ] + #[ former( default = true ) ] unquoting : bool, - #[ default( true ) ] + #[ former( default = true ) ] parsing_arrays : bool, - #[ default( false ) ] + #[ former( default = false ) ] several_values : bool, - #[ default( false ) ] + #[ former( default = false ) ] subject_win_paths_maybe : bool, } diff --git a/module/core/test_tools/Cargo.toml b/module/core/test_tools/Cargo.toml index 26b95f2926..18c79678fc 100644 --- a/module/core/test_tools/Cargo.toml +++ b/module/core/test_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_tools" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -38,8 +38,8 @@ no_std = [ # "typing_tools/no_std", # "data_type/no_std", # "diagnostics_tools/no_std", - # "process_tools/no_std", - "former/use_alloc", + # "process_tools_published/no_std", + # "former_stable/use_alloc", ] use_alloc = [ "no_std", @@ -49,8 +49,8 @@ use_alloc = [ # "typing_tools/use_alloc", # "data_type/use_alloc", # "diagnostics_tools/use_alloc", - # "process_tools/use_alloc", - "former/use_alloc", + # "process_tools_published/use_alloc", + # "former_stable/use_alloc", ] enabled = [ "error_tools/enabled", @@ -59,7 +59,7 @@ enabled = [ "typing_tools/enabled", "data_type/enabled", "diagnostics_tools/enabled", - "process_tools/enabled", + "process_tools_published/enabled", ] # nightly = [ "typing_tools/nightly" ] @@ -82,8 +82,8 @@ mem_tools = { workspace = true, features = [ "full" ] } typing_tools = { workspace = true, features = [ "full" ] } data_type = { workspace = true, features = [ "full" ] } diagnostics_tools = { workspace = true, features = [ "full" ] } -process_tools = { workspace = true, features = [ "full" ] } -former = { workspace = true, features = [ "full" ] } +process_tools_published = { workspace = true, features = [ "full" ] } +# former_stable = { workspace = true, features = [ "full" ] } [build-dependencies] rustc_version = "0.4" diff --git a/module/core/test_tools/Readme.md b/module/core/test_tools/Readme.md index 3d6a915053..f94ee9a113 100644 --- a/module/core/test_tools/Readme.md +++ b/module/core/test_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: test_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/test_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftest_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20test_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/test_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/test_tools/examples/test_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/test_tools/examples/test_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools for writing and running tests. diff --git a/module/core/test_tools/examples/test_tools_trivial.rs b/module/core/test_tools/examples/test_tools_trivial.rs new file mode 100644 index 0000000000..d69ffd9120 --- /dev/null +++ b/module/core/test_tools/examples/test_tools_trivial.rs @@ -0,0 +1,4 @@ +//! Example of using `test_tools`. +fn main() +{ +} diff --git a/module/core/test_tools/examples/test_tools_trivial/Cargo.toml b/module/core/test_tools/examples/test_tools_trivial/Cargo.toml deleted file mode 100644 index 1df5c58e17..0000000000 --- a/module/core/test_tools/examples/test_tools_trivial/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "test_tools_trivial" -version = "0.0.0" -edition = "2021" -publish = false - -[dependencies] -test_tools = { workspace = true } diff --git a/module/core/test_tools/examples/test_tools_trivial/Readme.md b/module/core/test_tools/examples/test_tools_trivial/Readme.md deleted file mode 100644 index 0fefd36750..0000000000 --- a/module/core/test_tools/examples/test_tools_trivial/Readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# Sample - -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=sample%2Frust%2Fwtest_trivial,SAMPLE_FILE=.%2Fsrc%2Fmain.rs/https://github.com/Wandalen/wTools) -[![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/wtest) diff --git a/module/core/test_tools/examples/test_tools_trivial/src/main.rs b/module/core/test_tools/examples/test_tools_trivial/src/main.rs deleted file mode 100644 index cca4967994..0000000000 --- a/module/core/test_tools/examples/test_tools_trivial/src/main.rs +++ /dev/null @@ -1,4 +0,0 @@ - -fn main() -{ -} diff --git a/module/core/test_tools/examples/test_tools_trivial/test/trivial_test.rs b/module/core/test_tools/examples/test_tools_trivial/test/trivial_test.rs deleted file mode 100644 index 105156ba2f..0000000000 --- a/module/core/test_tools/examples/test_tools_trivial/test/trivial_test.rs +++ /dev/null @@ -1,26 +0,0 @@ -use wtest::*; - -tests_impls! -{ - - fn pass1_test() - { - assert_eq!( true, true ); - } - - // - - fn pass2_test() - { - assert_eq!( 1, 1 ); - } - -} - -// - -tests_index! -{ - pass1_test, - pass2_test, -} diff --git a/module/core/test_tools/src/lib.rs b/module/core/test_tools/src/lib.rs index ed762f40fe..babcb96c49 100644 --- a/module/core/test_tools/src/lib.rs +++ b/module/core/test_tools/src/lib.rs @@ -39,9 +39,13 @@ pub mod dependency #[ doc( inline ) ] #[ allow( unused_imports ) ] pub use ::diagnostics_tools; - // #[ doc( inline ) ] - // #[ allow( unused_imports ) ] - // pub use ::process_tools; + + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use ::process_tools_published; + #[ doc( inline ) ] + #[ allow( unused_imports ) ] + pub use ::process_tools_published as process_tools; } diff --git a/module/core/test_tools/src/test/smoke_test.rs b/module/core/test_tools/src/test/smoke_test.rs index 8c671f72fc..29016a79bf 100644 --- a/module/core/test_tools/src/test/smoke_test.rs +++ b/module/core/test_tools/src/test/smoke_test.rs @@ -11,7 +11,8 @@ /// Internal namespace. pub( crate ) mod private { - use process_tools::environment; + use crate::*; + use dependency::process_tools::environment; // zzz : comment out // pub mod environment // { diff --git a/module/core/time_tools/Readme.md b/module/core/time_tools/Readme.md index d13bbb5b90..8a5725a0f6 100644 --- a/module/core/time_tools/Readme.md +++ b/module/core/time_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: time_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/time_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/time_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftime_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20time_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_time_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/time_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/time_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/time_tools/examples/time_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/time_tools/examples/time_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose time tools. diff --git a/module/core/time_tools/examples/time_tools_trivial_sample.rs b/module/core/time_tools/examples/time_tools_trivial.rs similarity index 100% rename from module/core/time_tools/examples/time_tools_trivial_sample.rs rename to module/core/time_tools/examples/time_tools_trivial.rs diff --git a/module/core/typing_tools/Cargo.toml b/module/core/typing_tools/Cargo.toml index 1919d0ddbd..22f2d3f982 100644 --- a/module/core/typing_tools/Cargo.toml +++ b/module/core/typing_tools/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typing_tools" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/typing_tools/Readme.md b/module/core/typing_tools/Readme.md index 042d605ed0..4376dde82c 100644 --- a/module/core/typing_tools/Readme.md +++ b/module/core/typing_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: typing_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/typing_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/typing_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftyping_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20typing_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_typing_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/typing_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/typing_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/typing_tools/examples/typing_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/typing_tools/examples/typing_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools for type checking. diff --git a/module/core/typing_tools/examples/typing_tools_trivial_sample.rs b/module/core/typing_tools/examples/typing_tools_trivial.rs similarity index 100% rename from module/core/typing_tools/examples/typing_tools_trivial_sample.rs rename to module/core/typing_tools/examples/typing_tools_trivial.rs diff --git a/module/core/variadic_from/Cargo.toml b/module/core/variadic_from/Cargo.toml index 3d1514bced..acd3790350 100644 --- a/module/core/variadic_from/Cargo.toml +++ b/module/core/variadic_from/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "variadic_from" -version = "0.13.0" +version = "0.14.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", diff --git a/module/core/variadic_from/Readme.md b/module/core/variadic_from/Readme.md index b86b5f7b82..5b0725f5ca 100644 --- a/module/core/variadic_from/Readme.md +++ b/module/core/variadic_from/Readme.md @@ -2,8 +2,7 @@ # Module :: variadic_from - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml)[![docs.rs](https://img.shields.io/docsrs/variadic_from?color=e3e8f0&logo=docs.rs)](https://docs.rs/variadic_from)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fvariadic_from_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20variadic_from_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_variadic_from_push.yml) [![docs.rs](https://img.shields.io/docsrs/variadic_from?color=e3e8f0&logo=docs.rs)](https://docs.rs/variadic_from) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/variadic_from/examples/variadic_from_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/variadic_from/examples/variadic_from_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Variadic from @@ -13,10 +12,11 @@ Variadic from ```rust -use variadic_from::exposed::*; - +#[ cfg( all(feature = "enabled", feature = "type_variadic_from" ) )] fn main() { + use variadic_from::exposed::*; + #[ derive( Debug, PartialEq, Default, VariadicFrom ) ] struct StructNamedFields { @@ -35,8 +35,10 @@ fn main() let got : StructNamedFields = ( 13, 14 ).to(); let exp = StructNamedFields{ a : 13, b : 14 }; assert_eq!( got, exp ); - } + +#[ cfg( not( all(feature = "enabled", feature = "type_variadic_from" ) ) ) ] +fn main(){} ``` ### To add to your project diff --git a/module/core/variadic_from/examples/variadic_from_trivial.rs b/module/core/variadic_from/examples/variadic_from_trivial.rs index 5909b238ab..d10d259566 100644 --- a/module/core/variadic_from/examples/variadic_from_trivial.rs +++ b/module/core/variadic_from/examples/variadic_from_trivial.rs @@ -1,8 +1,13 @@ //! qqq : write proper description -use variadic_from::exposed::*; +#[ cfg( not( all(feature = "enabled", feature = "type_variadic_from" ) ) ) ] +fn main(){} + +#[ cfg( all(feature = "enabled", feature = "type_variadic_from" ) )] fn main() { + use variadic_from::*; + #[ derive( Debug, PartialEq, Default, VariadicFrom ) ] struct StructNamedFields { @@ -22,4 +27,4 @@ fn main() let exp = StructNamedFields{ a : 13, b : 14 }; assert_eq!( got, exp ); -} +} \ No newline at end of file diff --git a/module/core/variadic_from/tests/variadic_from_tests.rs b/module/core/variadic_from/tests/variadic_from_tests.rs index 0474ad11d6..2b5d216700 100644 --- a/module/core/variadic_from/tests/variadic_from_tests.rs +++ b/module/core/variadic_from/tests/variadic_from_tests.rs @@ -4,5 +4,6 @@ use variadic_from as the_module; use test_tools::exposed::*; // #[ path = "inc.rs" ] +#[ cfg( feature = "enabled" ) ] mod inc; diff --git a/module/core/wtools/Cargo.toml b/module/core/wtools/Cargo.toml index c6b7ed81ca..78c0329a51 100644 --- a/module/core/wtools/Cargo.toml +++ b/module/core/wtools/Cargo.toml @@ -185,7 +185,7 @@ error_full = [ ] # error_use_std = [ "error", "error_tools/use_std" ] error_no_std = [ "error", "error_tools/no_std" ] -error_use_alloc = [ "error", "error_tools/use_alloc" ] +#error_use_alloc = [ "error", "error_tools/use_alloc" ] error_for_lib = [ "error", "error_tools/error_for_lib" ] error_for_app = [ "error", "error_tools/error_for_app" ] @@ -300,7 +300,7 @@ derive_is_variant = [ "derive", "derive_tools/derive_is_variant" ] derive_unwrap = [ "derive", "derive_tools/derive_unwrap" ] # derive_convert_case = [ "derive", "derive_tools/derive_convert_case" ] -derive_strum = [ "derive", "derive_tools/strum_derive" ] +derive_strum = [ "derive", "derive_tools/derive_strum" ] derive_strum_phf = [ "derive", "derive_tools/strum_phf" ] derive_display = [ "derive", "derive_tools/derive_display", "parse-display" ] diff --git a/module/core/wtools/Readme.md b/module/core/wtools/Readme.md index 6b776a16ed..5bd27d0838 100644 --- a/module/core/wtools/Readme.md +++ b/module/core/wtools/Readme.md @@ -2,8 +2,7 @@ # Module :: wtools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml)[![docs.rs](https://img.shields.io/docsrs/wtools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwtools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wtools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wtools_push.yml) [![docs.rs](https://img.shields.io/docsrs/wtools?color=e3e8f0&logo=docs.rs)](https://docs.rs/wtools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/core/wtools/examples/wtools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/core/wtools/examples/wtools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Collection of general purpose tools for solving problems. Fundamentally extend the language without spoiling, so may be used solely or in conjunction with another module of such kind. diff --git a/module/core/wtools/examples/main.rs b/module/core/wtools/examples/wtools_trivial.rs similarity index 100% rename from module/core/wtools/examples/main.rs rename to module/core/wtools/examples/wtools_trivial.rs diff --git a/module/core/wtools/examples/wtools_trivial_sample/Cargo.toml b/module/core/wtools/examples/wtools_trivial_sample/Cargo.toml deleted file mode 100644 index 4e5d1cf22d..0000000000 --- a/module/core/wtools/examples/wtools_trivial_sample/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "wtools_trivial" -version = "0.0.0" -edition = "2021" -publish = false - -[features] -typing = [] -default = [ "typing" ] - -[dependencies] -wtools = { workspace = true } diff --git a/module/core/wtools/examples/wtools_trivial_sample/Readme.md b/module/core/wtools/examples/wtools_trivial_sample/Readme.md deleted file mode 100644 index f0806ec263..0000000000 --- a/module/core/wtools/examples/wtools_trivial_sample/Readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# Sample - -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) -[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=sample%2Frust%2Fwtools_trivial,SAMPLE_FILE=.%2Fsrc%2Fmain.rs/https://github.com/Wandalen/wTools) -[![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/wtools) diff --git a/module/core/wtools/src/lib.rs b/module/core/wtools/src/lib.rs index d1243cc22c..6f8412f139 100644 --- a/module/core/wtools/src/lib.rs +++ b/module/core/wtools/src/lib.rs @@ -135,7 +135,7 @@ pub mod exposed #[ cfg( feature = "typing" ) ] pub use super::typing::exposed::*; #[ cfg( feature = "diagnostics" ) ] - pub use super::diag::exposed::*; + pub use super::diagnostics::diag::exposed::*; #[ cfg( any( feature = "dt", feature = "data_type" ) ) ] pub use super::dt::exposed::*; #[ cfg( feature = "time" ) ] @@ -176,7 +176,7 @@ pub mod prelude #[ cfg( feature = "diagnostics" ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] - pub use super::diag::prelude::*; + pub use super::diagnostics::diag::prelude::*; #[ cfg( any( feature = "dt", feature = "data_type" ) ) ] #[ doc( inline ) ] #[ allow( unused_imports ) ] diff --git a/module/core/wtools/tests/wtools_tests.rs b/module/core/wtools/tests/wtools_tests.rs index 0257700daf..a43dc170db 100644 --- a/module/core/wtools/tests/wtools_tests.rs +++ b/module/core/wtools/tests/wtools_tests.rs @@ -12,7 +12,7 @@ use test_tools::exposed::*; // } #[ cfg( feature = "iter_tools" ) ] -#[ path = "../../../core/iter_tools/tests/iter_tools_tests.rs" ] +#[ path = "../../../core/iter_tools/tests/tests.rs" ] mod iter_tools; #[ cfg( feature = "meta_tools" ) ] @@ -41,7 +41,7 @@ mod strs_tools; mod error_tools; #[ cfg( feature = "derive_tools" ) ] -#[ path = "../../../core/derive_tools/tests/derive_tests.rs" ] +#[ path = "../../../core/derive_tools/tests/tests.rs" ] mod derive_tools; #[ cfg( feature = "data_type" ) ] diff --git a/module/move/crates_tools/Readme.md b/module/move/crates_tools/Readme.md index 44e3875f77..6c6a1e3740 100644 --- a/module/move/crates_tools/Readme.md +++ b/module/move/crates_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: crates_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/crates_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/crates_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fcrates_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20crates_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_crates_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/crates_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/crates_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/move/crates_tools/examples/crates_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/move/crates_tools/examples/crates_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Tools to analyse crate files. diff --git a/module/move/crates_tools/examples/show_crate_content.rs b/module/move/crates_tools/examples/crates_tools_trivial.rs similarity index 100% rename from module/move/crates_tools/examples/show_crate_content.rs rename to module/move/crates_tools/examples/crates_tools_trivial.rs diff --git a/module/move/deterministic_rand/Cargo.toml b/module/move/deterministic_rand/Cargo.toml index c2cf7310fb..d0cc6d1fe5 100644 --- a/module/move/deterministic_rand/Cargo.toml +++ b/module/move/deterministic_rand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deterministic_rand" -version = "0.4.0" +version = "0.5.0" edition = "2021" authors = [ "Kostiantyn Wandalen ", @@ -47,4 +47,4 @@ rayon = "1.8.0" [[example]] name = "sample_deterministic_rand_trivial" -path = "examples/sample_deterministic_rand_trivial.rs" +path = "examples/deterministic_rand_trivial.rs" diff --git a/module/move/deterministic_rand/Readme.md b/module/move/deterministic_rand/Readme.md index 01b6b0d8c6..e0fd31ebde 100644 --- a/module/move/deterministic_rand/Readme.md +++ b/module/move/deterministic_rand/Readme.md @@ -2,8 +2,7 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml)[![docs.rs](https://img.shields.io/docsrs/deterministic_rand?color=e3e8f0&logo=docs.rs)](https://docs.rs/deterministic_rand)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fdeterministic_rand_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20deterministic_rand_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_deterministic_rand_push.yml) [![docs.rs](https://img.shields.io/docsrs/deterministic_rand?color=e3e8f0&logo=docs.rs)](https://docs.rs/deterministic_rand) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/move/deterministic_rand/examples/deterministic_rand_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/move/deterministic_rand/examples/deterministic_rand_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Hierarchical random number generators for concurrent simulations with switchable determinism. diff --git a/module/move/deterministic_rand/examples/sample_deterministic_rand_trivial.rs b/module/move/deterministic_rand/examples/deterministic_rand_trivial.rs similarity index 100% rename from module/move/deterministic_rand/examples/sample_deterministic_rand_trivial.rs rename to module/move/deterministic_rand/examples/deterministic_rand_trivial.rs diff --git a/module/move/graphs_tools/Cargo.toml b/module/move/graphs_tools/Cargo.toml index a6a4e8f6f5..64e17ebbd0 100644 --- a/module/move/graphs_tools/Cargo.toml +++ b/module/move/graphs_tools/Cargo.toml @@ -42,6 +42,7 @@ meta_tools = { workspace = true, features = [ "default" ] } iter_tools = { workspace = true, features = [ "default" ] } data_type = { workspace = true, features = [ "default" ] } strs_tools = { workspace = true, features = [ "default" ] } +derive_tools = { workspace = true, features = [ "default" ] } # type_constructor ={ workspace = true, features = [ "default" ] } [dev-dependencies] diff --git a/module/move/graphs_tools/Readme.md b/module/move/graphs_tools/Readme.md index 2e01fbdf26..455b0c9abb 100644 --- a/module/move/graphs_tools/Readme.md +++ b/module/move/graphs_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: graphs_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/graphs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/graphs_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fgraphs_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20graphs_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_graphs_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/graphs_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/graphs_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/move/graphs_tools/examples/graphs_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/move/graphs_tools/examples/graphs_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Graphs tools. diff --git a/module/move/graphs_tools/examples/graphs_tools_trivial_sample.rs b/module/move/graphs_tools/examples/graphs_tools_trivial.rs similarity index 100% rename from module/move/graphs_tools/examples/graphs_tools_trivial_sample.rs rename to module/move/graphs_tools/examples/graphs_tools_trivial.rs diff --git a/module/move/graphs_tools/src/abs/identity.rs b/module/move/graphs_tools/src/abs/identity.rs index 05fb1a1d05..806096847e 100644 --- a/module/move/graphs_tools/src/abs/identity.rs +++ b/module/move/graphs_tools/src/abs/identity.rs @@ -57,7 +57,7 @@ pub( crate ) mod private // } /// - /// Interface to identify an instance of somthing with ability to increase it to generate a new one. + /// Interface to identify an instance of something with ability to increase it to generate a new one. /// pub trait IdentityGeneratorInterface< Id > diff --git a/module/move/graphs_tools/src/canonical/identity.rs b/module/move/graphs_tools/src/canonical/identity.rs index 8c5ecd9128..d95c774912 100644 --- a/module/move/graphs_tools/src/canonical/identity.rs +++ b/module/move/graphs_tools/src/canonical/identity.rs @@ -114,12 +114,17 @@ pub( crate ) mod private // = // - type_constructor::types! - { + // type_constructor::types! + // { + // /// Identify an instance by integer. + // #[ derive( PartialEq, Eq, Copy, Clone, Hash ) ] + // pub single IdentityWithInt : isize; + // } + + /// Identify an instance by integer. - #[ derive( PartialEq, Eq, Copy, Clone, Hash ) ] - pub single IdentityWithInt : isize; - } + #[ derive( PartialEq, Eq, Copy, Clone, Hash, derive_tools::From, derive_tools::Deref ) ] + pub struct IdentityWithInt( isize ); /// /// Interface to to generate a new IDs for IdentityWithInt diff --git a/module/move/graphs_tools/tests/inc/identity_test.rs b/module/move/graphs_tools/tests/inc/identity_test.rs index a4f9296b86..aa85003e52 100644 --- a/module/move/graphs_tools/tests/inc/identity_test.rs +++ b/module/move/graphs_tools/tests/inc/identity_test.rs @@ -56,13 +56,13 @@ tests_impls! // } /* test.case( "from x2 tupple" ) */ - { - use type_constructor::VectorizedInto; - let src = ( 1, 3 ); - let got : ( IdentityWithInt, IdentityWithInt ) = src.vectorized_into(); - let exp = ( IdentityWithInt::from( 1 ), IdentityWithInt::from( 3 ) ); - a_id!( got, exp ); - } + // { + // //use type_constructor::VectorizedInto; + // let src = ( 1, 3 ); + // let got : ( IdentityWithInt, IdentityWithInt ) = src.into(); + // let exp = ( IdentityWithInt::from( 1 ), IdentityWithInt::from( 3 ) ); + // a_id!( got, exp ); + // } // /* test.case( "from x3 tupple" ) */ // { diff --git a/module/move/optimization_tools/.cargo/config.toml b/module/move/optimization_tools/.cargo/config.toml new file mode 100644 index 0000000000..ce93c42ac4 --- /dev/null +++ b/module/move/optimization_tools/.cargo/config.toml @@ -0,0 +1,3 @@ +[patch.crates-io] +pathfinder_geometry = { git = "https://github.com/servo/pathfinder.git" } +pathfinder_simd = { git = "https://github.com/servo/pathfinder.git" } \ No newline at end of file diff --git a/module/move/optimization_tools/Cargo.toml b/module/move/optimization_tools/Cargo.toml index 79f8029589..de8500b846 100644 --- a/module/move/optimization_tools/Cargo.toml +++ b/module/move/optimization_tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "optimization_tools" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = [ "Kostiantyn Wandalen " @@ -31,13 +31,13 @@ full = [ "enabled", ] enabled = [] -rapidity_6 = [] # to enable slow tests +# rapidity_6 = [] # to enable slow tests static_plot = [] -dynamic_plot = [ "static_plot", "plotters-backend", "piston_window" ] -lp_parse = [ "exmex" ] +dynamic_plot = [ "static_plot", "dep:plotters-backend", "dep:piston_window" ] +lp_parse = [ "dep:exmex" ] [dependencies] -derive_tools = { workspace = true, features = [ "derive_more", "full" ] } +derive_tools = { workspace = true, features = [ "derive_more", "full", "strum" ] } deterministic_rand = { workspace = true, features = [ "default" ] } iter_tools = { workspace = true, features = [ "default" ] } meta_tools = { workspace = true, features = [ "meta_constructors" ] } @@ -48,7 +48,15 @@ rand = "0.8.5" statrs = "0.16.0" faer = { version = "0.16.0", features = [ "ndarray" ] } ndarray = "0.15.6" -plotters = { version = "0.3.5" } +plotters = { version = "0.3.5", default-features = false, features = [ + "bitmap_encoder", + "ttf", + "area_series", + "point_series", + "line_series", + "full_palette", + "bitmap_backend", +] } plotters-backend = { version = "0.3.5", optional = true } piston_window = { version = "0.120.0", optional = true } exmex = { version = "0.18.0", features = [ "partial" ], optional = true } diff --git a/module/move/optimization_tools/Readme.md b/module/move/optimization_tools/Readme.md index 15db174a1d..3319f7f6d5 100644 --- a/module/move/optimization_tools/Readme.md +++ b/module/move/optimization_tools/Readme.md @@ -2,8 +2,7 @@ # Module :: optimization_tools - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml)[![docs.rs](https://img.shields.io/docsrs/optimization_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/optimization_tools)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Foptimization_tools_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20optimization_tools_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_optimization_tools_push.yml) [![docs.rs](https://img.shields.io/docsrs/optimization_tools?color=e3e8f0&logo=docs.rs)](https://docs.rs/optimization_tools) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/move/optimization_tools/examples/optimization_tools_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/move/optimization_tools/examples/optimization_tools_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) # Hybrid optimization using Simulated Annealing and Genetic Algorithm diff --git a/module/move/optimization_tools/examples/custom_problem.rs b/module/move/optimization_tools/examples/optimization_tools_trivial.rs similarity index 100% rename from module/move/optimization_tools/examples/custom_problem.rs rename to module/move/optimization_tools/examples/optimization_tools_trivial.rs diff --git a/module/move/optimization_tools/src/hybrid_optimizer/mod.rs b/module/move/optimization_tools/src/hybrid_optimizer/mod.rs index ac91811d33..90f381f6b6 100644 --- a/module/move/optimization_tools/src/hybrid_optimizer/mod.rs +++ b/module/move/optimization_tools/src/hybrid_optimizer/mod.rs @@ -8,7 +8,7 @@ use iter_tools::Itertools; use std::ops::RangeInclusive; use rayon::iter::{ ParallelIterator, IndexedParallelIterator}; use deterministic_rand::{ Seed, seq::{ SliceRandom, IteratorRandom } }; -use derive_tools::Display; +use derive_tools::exposed::Display; use optimal_params_search::OptimalProblem; mod gen_alg; diff --git a/module/move/optimization_tools/src/hybrid_optimizer/sim_anneal.rs b/module/move/optimization_tools/src/hybrid_optimizer/sim_anneal.rs index 112760b289..c176729441 100644 --- a/module/move/optimization_tools/src/hybrid_optimizer/sim_anneal.rs +++ b/module/move/optimization_tools/src/hybrid_optimizer/sim_anneal.rs @@ -1,6 +1,6 @@ //! Implementation of Simulated Annealing for Hybrid Optimizer. -use derive_tools::{ FromInner, InnerFrom, Display }; +use derive_tools::{ FromInner, InnerFrom, exposed::Display }; /// Represents temperature of SA process. #[ derive( Default, Debug, Display, Clone, Copy, PartialEq, PartialOrd, FromInner, InnerFrom ) ] pub struct Temperature( f64 ); diff --git a/module/move/optimization_tools/src/optimal_params_search/mod.rs b/module/move/optimization_tools/src/optimal_params_search/mod.rs index 76b84b8492..39390502e0 100644 --- a/module/move/optimization_tools/src/optimal_params_search/mod.rs +++ b/module/move/optimization_tools/src/optimal_params_search/mod.rs @@ -31,7 +31,7 @@ impl Default for OptimalParamsConfig { improvement_threshold : 0.005, max_no_improvement_steps : 10, - max_iterations : 100, + max_iterations : 50, } } } diff --git a/module/move/optimization_tools/src/optimal_params_search/nelder_mead.rs b/module/move/optimization_tools/src/optimal_params_search/nelder_mead.rs index 6a93d13e2f..cf90936c8b 100644 --- a/module/move/optimization_tools/src/optimal_params_search/nelder_mead.rs +++ b/module/move/optimization_tools/src/optimal_params_search/nelder_mead.rs @@ -65,7 +65,9 @@ impl Constraints #[ derive( Debug, Clone ) ] pub struct Stats { - + pub number_of_iterations : usize, + pub number_of_starting_points : usize, + pub resumed_after_stale : usize, pub starting_point : Point, pub differences : Vec< Vec< f64 > >, pub positive_change : Vec< usize >, @@ -79,6 +81,9 @@ impl Stats let dimensions = starting_point.coords.len(); Self { + number_of_iterations : 0, + number_of_starting_points : 1, + resumed_after_stale : 0, starting_point, differences : vec![ Vec::new(); dimensions ], positive_change : vec![ 0; dimensions ], @@ -488,6 +493,7 @@ where R : RangeBounds< f64 > + Sync, let results = points.into_par_iter().map( | point | { let mut stats = Stats::new( point.clone() ); + stats.number_of_starting_points = points_number; let x0 = point.clone(); let dimensions = x0.coords.len(); let mut prev_best = self.evaluate_point( &x0, &mut stats ); @@ -509,7 +515,8 @@ where R : RangeBounds< f64 > + Sync, if self.max_iterations <= iterations { - return Result::< Solution, Error >::Ok ( Solution + stats.number_of_iterations = iterations; + return Result::< Solution, Error >::Ok ( Solution { point : res[ 0 ].0.clone(), objective : res[ 0 ].1, @@ -517,11 +524,13 @@ where R : RangeBounds< f64 > + Sync, stats : Some( stats ), } ) } - - iterations += 1; - + if best.1 < prev_best - self.improvement_threshold { + if steps_with_no_improv > 0 + { + stats.resumed_after_stale += 1; + } steps_with_no_improv = 0; prev_best = best.1; } @@ -532,7 +541,8 @@ where R : RangeBounds< f64 > + Sync, if steps_with_no_improv >= self.max_no_improvement_steps { - return Ok ( Solution + stats.number_of_iterations = iterations; + return Ok ( Solution { point : res[ 0 ].0.clone(), objective : res[ 0 ].1, @@ -541,6 +551,8 @@ where R : RangeBounds< f64 > + Sync, } ) } + iterations += 1; + //centroid let mut x0_center = vec![ 0.0; dimensions ]; for ( point, _ ) in res.iter().take( res.len() - 1 ) @@ -569,7 +581,6 @@ where R : RangeBounds< f64 > + Sync, let prev_point = res.pop().unwrap().0; stats.record_positive_change( &prev_point, &x_ref ); res.push( ( x_ref, reflection_score ) ); - // log::info!("reflection"); continue; } @@ -591,7 +602,6 @@ where R : RangeBounds< f64 > + Sync, let prev_point = res.pop().unwrap().0; stats.record_positive_change( &prev_point, &x_exp ); res.push( ( x_exp, expansion_score ) ); - // log::info!("expansion"); continue; } @@ -600,7 +610,6 @@ where R : RangeBounds< f64 > + Sync, let prev_point = res.pop().unwrap().0; stats.record_positive_change( &prev_point, &x_ref ); res.push( ( x_ref, reflection_score ) ); - // log::info!("expansion"); continue; } } @@ -620,7 +629,6 @@ where R : RangeBounds< f64 > + Sync, let prev_point = res.pop().unwrap().0; stats.record_positive_change( &prev_point, &x_con ); res.push( ( x_con, contraction_score ) ); - // log::info!("contraction"); continue; } @@ -639,7 +647,6 @@ where R : RangeBounds< f64 > + Sync, let score = self.evaluate_point( &x_shrink, &mut stats ); new_res.push( ( x_shrink, score ) ); } - // log::info!("shrink"); res = new_res; } } ).collect::< Vec< _ > >(); @@ -828,7 +835,7 @@ pub struct Solution } /// Reasons for termination of optimization process. -#[ derive( Debug, Clone ) ] +#[ derive( Debug, Clone, derive_tools::Display ) ] pub enum TerminationReason { /// Reached limit of total iterations. diff --git a/module/move/optimization_tools/src/problems/sudoku/cell_val.rs b/module/move/optimization_tools/src/problems/sudoku/cell_val.rs index f5b5394b95..f17b3db378 100644 --- a/module/move/optimization_tools/src/problems/sudoku/cell_val.rs +++ b/module/move/optimization_tools/src/problems/sudoku/cell_val.rs @@ -1,7 +1,7 @@ //! Contains CellVal structure that corresponds to single digit on Sudoku field. //! -use derive_tools::Display; +use derive_tools::exposed::Display; /// Represents the value of a cell in Sudoku. It can have a value from 1 to 9 or 0 if the cell is not assigned. #[ derive( Default, Debug, Display, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash ) ] diff --git a/module/move/optimization_tools/src/problems/sudoku/sudoku.rs b/module/move/optimization_tools/src/problems/sudoku/sudoku.rs index e71e3bb1d6..b016fa4cda 100644 --- a/module/move/optimization_tools/src/problems/sudoku/sudoku.rs +++ b/module/move/optimization_tools/src/problems/sudoku/sudoku.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; use crate::hybrid_optimizer::*; use crate::problems::sudoku::*; -use derive_tools::{ FromInner, InnerFrom, Display }; +use derive_tools::{ FromInner, InnerFrom, exposed::Display }; use deterministic_rand::{ Hrng, Rng, seq::SliceRandom }; use iter_tools::Itertools; @@ -12,28 +12,11 @@ use iter_tools::Itertools; trait BoardExt { /// Validate that each bloack has at least one non-fixed cell. - fn validate_each_block_has_non_fixed_cell( &self ) -> bool; fn validate_block_has_non_fixed_cells( &self, block : BlockIndex ) -> bool; } impl BoardExt for Board { - fn validate_each_block_has_non_fixed_cell( &self ) -> bool - { - for block in self.blocks() - { - let fixed = self.block_cells( block ) - .map( | cell | self.cell( cell ) ) - .fold( 0, | acc, e | if e == 0.into() { acc + 1 } else { acc } ) - ; - if fixed <= 1 || fixed >= 10 - { - return false; - } - } - true - } - fn validate_block_has_non_fixed_cells( &self, block : BlockIndex ) -> bool { let fixed = self.block_cells( block ) diff --git a/module/move/optimization_tools/sudoku_results.md b/module/move/optimization_tools/sudoku_results.md index e346a06069..d6b9c2b786 100644 --- a/module/move/optimization_tools/sudoku_results.md +++ b/module/move/optimization_tools/sudoku_results.md @@ -2,19 +2,23 @@ ## For hybrid: - - max number of iterations: 100 + - max number of iterations: 50 - max no improvement iterations : 10 - improvement threshold : 0.005s - - calculated points: 19 from 48 + - termination reason: NoImprovement - - points from cache: 29 from 48 + - iterations number: 48 + + - resumed after stale: 8 + + - points from cache: 43/133 - level: Easy - - execution time: 0.154s + - execution time: 0.117s - parameters: @@ -22,32 +26,32 @@ ┌─────────────┬────────┬────────┬─────────┬─────────────┬──────────┬─────────┬────────┐ │ │ start │ min │ max │ sum of diff │ expected │ changes │ final │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ temperature │ 0.8561 │ 0.00 │ 1.00 │ 0.02 │ 0.00 │ 9 │ 0.9995 │ +│ temperature │ 0.4043 │ 0.00 │ 1.00 │ 0.10 │ 0.00 │ 41 │ 1.0000 │ │ decrease │ │ │ │ │ │ │ │ │ coefficient │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 106 │ 10.00 │ 200.00 │ 311.97 │ 7.43 │ 9 │ 108 │ +│ max │ 37 │ 10.00 │ 200.00 │ 8265.03 │ 65.08 │ 41 │ 177 │ │ mutations │ │ │ │ │ │ │ │ │ per │ │ │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ mutation │ 0.42 │ 0.00 │ 1.00 │ 1.31 │ 0.03 │ 9 │ 0.23 │ +│ mutation │ 0.16 │ 0.00 │ 1.00 │ 17.64 │ 0.14 │ 41 │ 0.41 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ crossover │ 0.66 │ 0.00 │ 1.00 │ 1.70 │ 0.04 │ 9 │ 0.54 │ +│ crossover │ 0.93 │ 0.00 │ 1.00 │ 42.41 │ 0.33 │ 41 │ 0.10 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ elitism │ -0.09 │ - │ - │ - │ - │ - │ 0.23 │ +│ elitism │ -0.09 │ - │ - │ - │ - │ - │ 0.49 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 81 │ 1.00 │ 100.00 │ 1404.93 │ 33.45 │ 9 │ 62 │ +│ max │ 30 │ 1.00 │ 100.00 │ 160.48 │ 1.26 │ 41 │ 31 │ │ stale │ │ │ │ │ │ │ │ │ iterations │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ population │ 116 │ 1.00 │ 1000.00 │ 9233.07 │ 219.83 │ 9 │ 3 │ +│ population │ 549 │ 1.00 │ 1000.00 │ 33602.75 │ 264.59 │ 41 │ 11 │ │ size │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ dynasties │ 249 │ 100.00 │ 2000.00 │ 19863.18 │ 472.93 │ 9 │ 1486 │ +│ dynasties │ 439 │ 100.00 │ 2000.00 │ 58761.38 │ 462.69 │ 41 │ 1521 │ │ limit │ │ │ │ │ │ │ │ └─────────────┴────────┴────────┴─────────┴─────────────┴──────────┴─────────┴────────┘ ``` @@ -59,7 +63,9 @@ - `max number of iterations` : limit of total iterations of optimization process, termination condition - `max no improvement iterations` : max amount of steps performed without detected improvement, termination condition - `improvement threshold` : minimal value detected as improvement in objective function result - - `calculated points` : new calculated points that were not found in cache + - `termination reason` : the reason why optimization process was stopped + - `iterations number` : actual number of iterations performed during optimization + - `resumed after stale` : how many times optimization progress was resumed after some iterations without improvement - `points from cache` : points calculated during previous optimizations and read from cache - `level` : sudoku board difficulty level - `execution time` : duration of shortest found hybrid optimization process using final parameters, measured in seconds @@ -73,19 +79,23 @@ - `final` : calculated value of parameter for which execution time was the lowest ## For SA: - - max number of iterations: 100 + - max number of iterations: 50 - max no improvement iterations : 10 - improvement threshold : 0.005s - - calculated points: 0 from 22 + - termination reason: NoImprovement + + - iterations number: 12 - - points from cache: 22 from 22 + - resumed after stale: 1 + + - points from cache: 31/32 - level: Easy - - execution time: 0.019s + - execution time: 0.026s - parameters: @@ -93,11 +103,11 @@ ┌─────────────┬────────┬────────┬─────────┬─────────────┬──────────┬─────────┬────────┐ │ │ start │ min │ max │ sum of diff │ expected │ changes │ final │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ temperature │ 0.8244 │ 0.00 │ 1.00 │ 0.48 │ 0.03 │ 12 │ 0.9554 │ +│ temperature │ 0.8244 │ 0.00 │ 1.00 │ 0.83 │ 0.03 │ 11 │ 0.9554 │ │ decrease │ │ │ │ │ │ │ │ │ coefficient │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 157 │ 10.00 │ 200.00 │ 261.00 │ 18.64 │ 12 │ 116 │ +│ max │ 157 │ 10.00 │ 200.00 │ 423.98 │ 17.67 │ 11 │ 116 │ │ mutations │ │ │ │ │ │ │ │ │ per │ │ │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ @@ -111,14 +121,14 @@ │ elitism │ -0.00 │ - │ - │ - │ - │ - │ 0.00 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 67 │ 1.00 │ 100.00 │ 214.24 │ 15.30 │ 12 │ 39 │ +│ max │ 67 │ 1.00 │ 100.00 │ 265.64 │ 11.07 │ 11 │ 39 │ │ stale │ │ │ │ │ │ │ │ │ iterations │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ │ population │ 1 │ 1.00 │ 1.00 │ 0.00 │ 0.00 │ 0 │ 1 │ │ size │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ dynasties │ 3455 │ 100.00 │ 5000.00 │ 13134.94 │ 938.21 │ 12 │ 1646 │ +│ dynasties │ 3455 │ 100.00 │ 5000.00 │ 17618.46 │ 734.10 │ 11 │ 1646 │ │ limit │ │ │ │ │ │ │ │ └─────────────┴────────┴────────┴─────────┴─────────────┴──────────┴─────────┴────────┘ ``` @@ -130,7 +140,9 @@ - `max number of iterations` : limit of total iterations of optimization process, termination condition - `max no improvement iterations` : max amount of steps performed without detected improvement, termination condition - `improvement threshold` : minimal value detected as improvement in objective function result - - `calculated points` : new calculated points that were not found in cache + - `termination reason` : the reason why optimization process was stopped + - `iterations number` : actual number of iterations performed during optimization + - `resumed after stale` : how many times optimization progress was resumed after some iterations without improvement - `points from cache` : points calculated during previous optimizations and read from cache - `level` : sudoku board difficulty level - `execution time` : duration of shortest found hybrid optimization process using final parameters, measured in seconds @@ -144,19 +156,23 @@ - `final` : calculated value of parameter for which execution time was the lowest ## For GA: - - max number of iterations: 100 + - max number of iterations: 50 - max no improvement iterations : 10 - improvement threshold : 0.005s - - calculated points: 81 from 120 + - termination reason: NoImprovement + + - iterations number: 30 + + - resumed after stale: 4 - - points from cache: 39 from 120 + - points from cache: 87/93 - level: Easy - - execution time: 0.263s + - execution time: 0.175s - parameters: @@ -164,32 +180,32 @@ ┌─────────────┬────────┬────────┬─────────┬─────────────┬──────────┬─────────┬────────┐ │ │ start │ min │ max │ sum of diff │ expected │ changes │ final │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ temperature │ 0.6847 │ 0.00 │ 1.00 │ 0.45 │ 0.00 │ 36 │ 0.9995 │ +│ temperature │ 0.3698 │ 0.00 │ 1.00 │ 4.51 │ 0.05 │ 25 │ 0.9432 │ │ decrease │ │ │ │ │ │ │ │ │ coefficient │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 174 │ 10.00 │ 200.00 │ 514.31 │ 4.40 │ 36 │ 97 │ +│ max │ 108 │ 10.00 │ 200.00 │ 751.96 │ 8.74 │ 25 │ 109 │ │ mutations │ │ │ │ │ │ │ │ │ per │ │ │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ mutation │ 0.78 │ 0.10 │ 1.00 │ 5.51 │ 0.05 │ 36 │ 0.22 │ +│ mutation │ 0.22 │ 0.10 │ 1.00 │ 4.71 │ 0.05 │ 25 │ 0.32 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ crossover │ 0.73 │ 0.10 │ 1.00 │ 2.09 │ 0.02 │ 36 │ 0.51 │ +│ crossover │ 0.16 │ 0.10 │ 1.00 │ 3.75 │ 0.04 │ 25 │ 0.54 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ elitism │ -0.52 │ - │ - │ - │ - │ - │ 0.26 │ +│ elitism │ 0.61 │ - │ - │ - │ - │ - │ 0.15 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 29 │ 1.00 │ 100.00 │ 134.61 │ 1.15 │ 36 │ 31 │ +│ max │ 61 │ 1.00 │ 100.00 │ 523.70 │ 6.09 │ 25 │ 35 │ │ stale │ │ │ │ │ │ │ │ │ iterations │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ population │ 846 │ 10.00 │ 2000.00 │ 24289.87 │ 207.61 │ 36 │ 84 │ +│ population │ 1743 │ 10.00 │ 2000.00 │ 29942.40 │ 348.17 │ 25 │ 12 │ │ size │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ dynasties │ 859 │ 100.00 │ 2000.00 │ 8440.12 │ 72.14 │ 36 │ 1075 │ +│ dynasties │ 1626 │ 100.00 │ 2000.00 │ 10424.65 │ 121.22 │ 25 │ 1092 │ │ limit │ │ │ │ │ │ │ │ └─────────────┴────────┴────────┴─────────┴─────────────┴──────────┴─────────┴────────┘ ``` @@ -201,7 +217,9 @@ - `max number of iterations` : limit of total iterations of optimization process, termination condition - `max no improvement iterations` : max amount of steps performed without detected improvement, termination condition - `improvement threshold` : minimal value detected as improvement in objective function result - - `calculated points` : new calculated points that were not found in cache + - `termination reason` : the reason why optimization process was stopped + - `iterations number` : actual number of iterations performed during optimization + - `resumed after stale` : how many times optimization progress was resumed after some iterations without improvement - `points from cache` : points calculated during previous optimizations and read from cache - `level` : sudoku board difficulty level - `execution time` : duration of shortest found hybrid optimization process using final parameters, measured in seconds @@ -221,11 +239,11 @@ │ │ coefficient │ per │ │ │ │ iterations │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ ├────────┼─────────────┼───────────┼──────────┼───────────┼─────────┼────────────┼────────────┼───────────┼───────────┤ -│ hybrid │ 0.9995 │ 108 │ 0.23 │ 0.54 │ 0.23 │ 62 │ 3 │ 1486 │ 0.154s │ +│ hybrid │ 1.0000 │ 177 │ 0.41 │ 0.10 │ 0.49 │ 31 │ 11 │ 1521 │ 0.117s │ ├────────┼─────────────┼───────────┼──────────┼───────────┼─────────┼────────────┼────────────┼───────────┼───────────┤ -│ SA │ 0.9554 │ 116 │ 1.00 │ 0.00 │ 0.00 │ 39 │ 1 │ 1646 │ 0.019s │ +│ SA │ 0.9554 │ 116 │ 1.00 │ 0.00 │ 0.00 │ 39 │ 1 │ 1646 │ 0.026s │ ├────────┼─────────────┼───────────┼──────────┼───────────┼─────────┼────────────┼────────────┼───────────┼───────────┤ -│ GA │ 0.9995 │ 97 │ 0.22 │ 0.51 │ 0.26 │ 31 │ 84 │ 1075 │ 0.263s │ +│ GA │ 0.9432 │ 109 │ 0.32 │ 0.54 │ 0.15 │ 35 │ 12 │ 1092 │ 0.175s │ └────────┴─────────────┴───────────┴──────────┴───────────┴─────────┴────────────┴────────────┴───────────┴───────────┘ ``` diff --git a/module/move/optimization_tools/tests/ga_optimization.rs b/module/move/optimization_tools/tests/ga_optimization.rs index cd51e39772..e42568aee6 100644 --- a/module/move/optimization_tools/tests/ga_optimization.rs +++ b/module/move/optimization_tools/tests/ga_optimization.rs @@ -54,9 +54,9 @@ fn crossover() /// /// # Usage /// -/// cargo test solve_with_ga --release --features rapidity_6 +/// cargo test solve_with_ga --release /// -#[ cfg( feature = "rapidity_6" ) ] +#[ ignore ] #[ test ] fn solve_with_ga() { diff --git a/module/move/optimization_tools/tests/opt_params.rs b/module/move/optimization_tools/tests/opt_params.rs index ad4250ad9e..d50ffdba76 100644 --- a/module/move/optimization_tools/tests/opt_params.rs +++ b/module/move/optimization_tools/tests/opt_params.rs @@ -230,7 +230,9 @@ fn write_results " - `max number of iterations` : limit of total iterations of optimization process, termination condition\n", " - `max no improvement iterations` : max amount of steps performed without detected improvement, termination condition\n", " - `improvement threshold` : minimal value detected as improvement in objective function result\n", - " - `calculated points` : new calculated points that were not found in cache\n", + " - `termination reason` : the reason why optimization process was stopped\n", + " - `iterations number` : actual number of iterations performed during optimization\n", + " - `resumed after stale` : how many times optimization progress was resumed after some iterations without improvement\n", " - `points from cache` : points calculated during previous optimizations and read from cache\n", ); @@ -337,138 +339,76 @@ fn find_opt_params_sudoku() -> Result< (), Box< dyn std::error::Error > > let config = OptimalParamsConfig::default(); let initial = SudokuInitial::new( Board::from( easy ) ); - let hybrid_problem = Problem::new - ( - initial.clone(), - BestRowsColumnsCrossover, - RandomPairInBlockMutation, - ); - let starting_params = hybrid_optimizer::starting_params_for_hybrid()?; - let res = optimal_params_search::find_hybrid_optimal_params - ( - config.clone(), - starting_params.clone(), - hybrid_problem, - Some( path.clone() ), - ); - assert!( res.is_ok() ); - let mut hybrid_res = Statistics::new(); - if let Ok( solution ) = res - { - let cached = solution.stats.clone().unwrap().cached_points; - hybrid_res = Statistics - { - table_params : named_results_list - ( - solution.point.coords - .into_iter() - .map( | val | val ) - .collect_vec(), - solution.stats.unwrap(), - starting_params.bounds, - ), - list_params : vec! - [ - ( String::from( "max number of iterations" ), format!( "{}", config.max_iterations ) ), - ( String::from( "max no improvement iterations " ), format!( "{}", config.max_no_improvement_steps ) ), - ( String::from( "improvement threshold " ), format!( "{}s", config.improvement_threshold ) ), - ( String::from( "calculated points" ), format!( "{} from {}", cached.1, cached.1 + cached.0 ) ), - ( String::from( "points from cache" ), format!( "{} from {}", cached.0, cached.1 + cached.0 ) ), - ( String::from( "level" ), format!( "{:?}", Board::from( easy ).calculate_level() ) ), - ( String::from( "execution time" ), format!( "{:.3}s", solution.objective ) ), - ] - } - } - - // SA - let hybrid_problem = Problem::new - ( - initial.clone(), - BestRowsColumnsCrossover, - RandomPairInBlockMutation, - ); - let starting_params = hybrid_optimizer::starting_params_for_sa()?; - let res = optimal_params_search::find_hybrid_optimal_params - ( - config.clone(), - starting_params.clone(), - hybrid_problem, - Some( path.clone() ), - ); - assert!( res.is_ok() ); - let mut sa_res = Statistics::new(); - if let Ok( solution ) = res + let mut ga_res = Statistics::new(); + for mode in [ "hybrid", "sa", "ga" ] { - let cached = solution.stats.clone().unwrap().cached_points; - sa_res = Statistics + let mut starting_params = hybrid_optimizer::starting_params_for_hybrid()?; + match mode { - table_params : named_results_list - ( - solution.point.coords - .into_iter() - .map( | val | val ) - .collect_vec(), - solution.stats.unwrap(), - starting_params.bounds, - ), - list_params : vec! - [ - ( String::from( "max number of iterations" ), format!( "{}", config.max_iterations ) ), - ( String::from( "max no improvement iterations " ), format!( "{}", config.max_no_improvement_steps ) ), - ( String::from( "improvement threshold " ), format!( "{}s", config.improvement_threshold ) ), - ( String::from( "calculated points" ), format!( "{} from {}", cached.1, cached.1 + cached.0 ) ), - ( String::from( "points from cache" ), format!( "{} from {}", cached.0, cached.1 + cached.0 ) ), - ( String::from( "level" ), format!( "{:?}", Board::from( easy ).calculate_level() ) ), - ( String::from( "execution time" ), format!( "{:.3}s", solution.objective ) ), - ] + "hybrid" => {}, + "sa" => starting_params = hybrid_optimizer::starting_params_for_sa()?, + "ga" => starting_params = hybrid_optimizer::starting_params_for_ga()?, + _ => unreachable!(), } - } - // GA - let hybrid_problem = Problem::new( - initial.clone(), - BestRowsColumnsCrossover, - RandomPairInBlockMutation, - ); - let starting_params = hybrid_optimizer::starting_params_for_ga()?; - let res = optimal_params_search::find_hybrid_optimal_params - ( - config.clone(), - starting_params.clone(), - hybrid_problem, - Some( path ), - ); - assert!( res.is_ok() ); + let hybrid_problem = Problem::new + ( + initial.clone(), + BestRowsColumnsCrossover, + RandomPairInBlockMutation, + ); - let mut ga_res = Statistics::new(); - if let Ok( solution ) = res - { - let cached = solution.stats.clone().unwrap().cached_points; - ga_res = Statistics + let res = optimal_params_search::find_hybrid_optimal_params + ( + config.clone(), + starting_params.clone(), + hybrid_problem, + Some( path.clone() ), + ); + assert!( res.is_ok() ); + + if let Ok( solution ) = res { - table_params : named_results_list - ( - solution.point.coords - .into_iter() - .map( | val | val ) - .collect_vec(), - solution.stats.unwrap(), - starting_params.bounds, - ), - list_params : vec! - [ - ( String::from( "max number of iterations" ), format!( "{}", config.max_iterations ) ), - ( String::from( "max no improvement iterations " ), format!( "{}", config.max_no_improvement_steps ) ), - ( String::from( "improvement threshold " ), format!( "{}s", config.improvement_threshold ) ), - ( String::from( "calculated points" ), format!( "{} from {}", cached.1, cached.1 + cached.0 ) ), - ( String::from( "points from cache" ), format!( "{} from {}", cached.0, cached.1 + cached.0 ) ), - ( String::from( "level" ), format!( "{:?}", Board::from( easy ).calculate_level() ) ), - ( String::from( "execution time" ), format!( "{:.3}s", solution.objective ) ), - ] + assert!( solution.stats.is_some() ); + let stats = solution.stats.clone().unwrap(); + let cached = stats.cached_points; + let final_res = Statistics + { + table_params : named_results_list + ( + solution.point.coords + .into_iter() + .map( | val | val ) + .collect_vec(), + solution.stats.unwrap(), + starting_params.bounds, + ), + list_params : vec! + [ + ( String::from( "max number of iterations" ), format!( "{}", config.max_iterations ) ), + ( String::from( "max no improvement iterations " ), format!( "{}", config.max_no_improvement_steps ) ), + ( String::from( "improvement threshold " ), format!( "{}s", config.improvement_threshold ) ), + ( String::from( "termination reason" ), format!( "{}", solution.reason ) ), + ( String::from( "iterations number" ), format!( "{}", stats.number_of_iterations ) ), + ( String::from( "resumed after stale" ), format!( "{}", stats.resumed_after_stale ) ), + ( String::from( "points from cache" ), format!( "{}/{}", cached.0, cached.1 + cached.0 ) ), + ( String::from( "level" ), format!( "{:?}", Board::from( easy ).calculate_level() ) ), + ( String::from( "execution time" ), format!( "{:.3}s", solution.objective ) ), + ] + }; + + match mode + { + "hybrid" => hybrid_res = final_res, + "sa" => sa_res = final_res, + "ga" => ga_res = final_res, + _ => unreachable!(), + } } } + write_results( String::from( "sudoku_results" ), String::from( "Sudoku Problem" ), hybrid_res, sa_res, ga_res )?; Ok( () ) } diff --git a/module/move/optimization_tools/tests/optimization.rs b/module/move/optimization_tools/tests/optimization.rs index a5540bcab1..329cfbe213 100644 --- a/module/move/optimization_tools/tests/optimization.rs +++ b/module/move/optimization_tools/tests/optimization.rs @@ -58,9 +58,9 @@ fn initial_temperature() /// /// # Usage /// -/// cargo test solve_with_sa --release --features rapidity_6 +/// cargo test solve_with_sa --release /// -#[ cfg( feature = "rapidity_6" ) ] +#[ ignore ] #[ test ] fn solve_with_sa() { @@ -103,9 +103,9 @@ fn solve_with_sa() /// /// # Usage /// -/// cargo test solve_empty_full_block --release --features rapidity_6 +/// cargo test solve_empty_full_block --release /// -#[ cfg( feature = "rapidity_6" ) ] +#[ ignore ] #[ test ] fn solve_empty_full_block() { @@ -179,9 +179,9 @@ fn solve_empty_full_block() /// /// # Usage /// -/// cargo test time_measure --release --features rapidity_6 +/// cargo test time_measure --release /// -#[ cfg( feature = "rapidity_6" ) ] +#[ ignore ] #[ test ] fn time_measure() { diff --git a/module/move/optimization_tools/tests/simplex.rs b/module/move/optimization_tools/tests/simplex.rs index b0a66c1b18..7310f74d39 100644 --- a/module/move/optimization_tools/tests/simplex.rs +++ b/module/move/optimization_tools/tests/simplex.rs @@ -112,6 +112,8 @@ fn problem_5_vars() assert_eq!( solution[ 0 ].point, vec![ 300.0, 400.0, 300.0, 0.0, 0.0 ] ) } +// for issue https://github.com/plotters-rs/plotters/issues/573 +#[ cfg( not( all( debug_assertions, target_os = "linux" ) ) ) ] #[ test ] fn problem_draw() { diff --git a/module/move/optimization_tools/tests/traveling_salesman.rs b/module/move/optimization_tools/tests/traveling_salesman.rs index 0b8d618a4e..513a1a86c9 100644 --- a/module/move/optimization_tools/tests/traveling_salesman.rs +++ b/module/move/optimization_tools/tests/traveling_salesman.rs @@ -82,6 +82,7 @@ fn tsp_person_mutate() a_id!( person.route.len() - 1, unique.len() ); } +#[ ignore ] #[ test ] fn find_route() { diff --git a/module/move/optimization_tools/tsp_results.md b/module/move/optimization_tools/tsp_results.md index 78b5195456..22ec13075b 100644 --- a/module/move/optimization_tools/tsp_results.md +++ b/module/move/optimization_tools/tsp_results.md @@ -2,19 +2,19 @@ ## For hybrid: - - max number of iterations: 100 + - max number of iterations: 50 - max no improvement iterations : 10 - improvement threshold : 0.005s - - calculated points: 124 from 133 + - calculated points: 79 from 79 - - points from cache: 9 from 133 + - points from cache: 0 from 79 - number of nodes: 4 - - execution time: 0.008s + - execution time: 0.018s - parameters: @@ -22,32 +22,32 @@ ┌─────────────┬────────┬────────┬─────────┬─────────────┬──────────┬─────────┬────────┐ │ │ start │ min │ max │ sum of diff │ expected │ changes │ final │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ temperature │ 0.7726 │ 0.00 │ 1.00 │ 28.88 │ 0.21 │ 74 │ 0.7349 │ +│ temperature │ 0.8572 │ 0.00 │ 1.00 │ 0.14 │ 0.00 │ 50 │ 0.9999 │ │ decrease │ │ │ │ │ │ │ │ │ coefficient │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 14 │ 10.00 │ 200.00 │ 6917.13 │ 49.76 │ 74 │ 33 │ +│ max │ 150 │ 10.00 │ 200.00 │ 2920.64 │ 35.19 │ 50 │ 54 │ │ mutations │ │ │ │ │ │ │ │ │ per │ │ │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ mutation │ 0.00 │ 0.00 │ 1.00 │ 23.18 │ 0.17 │ 74 │ 0.13 │ +│ mutation │ 0.57 │ 0.00 │ 1.00 │ 21.60 │ 0.26 │ 50 │ 0.02 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ crossover │ 0.63 │ 0.00 │ 1.00 │ 40.81 │ 0.29 │ 74 │ 0.86 │ +│ crossover │ 0.56 │ 0.00 │ 1.00 │ 17.49 │ 0.21 │ 50 │ 0.31 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ elitism │ 0.37 │ - │ - │ - │ - │ - │ 0.01 │ +│ elitism │ -0.13 │ - │ - │ - │ - │ - │ 0.66 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 58 │ 1.00 │ 100.00 │ 3695.03 │ 26.58 │ 74 │ 62 │ +│ max │ 35 │ 1.00 │ 100.00 │ 152.19 │ 1.83 │ 50 │ 30 │ │ stale │ │ │ │ │ │ │ │ │ iterations │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ population │ 674 │ 1.00 │ 1000.00 │ 46923.94 │ 337.58 │ 74 │ 1 │ +│ population │ 148 │ 1.00 │ 1000.00 │ 20174.02 │ 243.06 │ 50 │ 10 │ │ size │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ dynasties │ 824 │ 100.00 │ 2000.00 │ 79548.00 │ 572.29 │ 74 │ 138 │ +│ dynasties │ 1982 │ 100.00 │ 2000.00 │ 63109.09 │ 760.35 │ 50 │ 130 │ │ limit │ │ │ │ │ │ │ │ └─────────────┴────────┴────────┴─────────┴─────────────┴──────────┴─────────┴────────┘ ``` @@ -59,7 +59,9 @@ - `max number of iterations` : limit of total iterations of optimization process, termination condition - `max no improvement iterations` : max amount of steps performed without detected improvement, termination condition - `improvement threshold` : minimal value detected as improvement in objective function result - - `calculated points` : new calculated points that were not found in cache + - `termination reason` : the reason why optimization process was stopped + - `iterations number` : actual number of iterations performed during optimization + - `resumed after stale` : how many times optimization progress was resumed after some iterations without improvement - `points from cache` : points calculated during previous optimizations and read from cache - `number of nodes` : number of nodes in graph representing cities from traveling salesman problem - `execution time` : duration of shortest found hybrid optimization process using final parameters, measured in seconds @@ -73,15 +75,15 @@ - `final` : calculated value of parameter for which execution time was the lowest ## For SA: - - max number of iterations: 100 + - max number of iterations: 50 - max no improvement iterations : 10 - improvement threshold : 0.005s - - calculated points: 16 from 26 + - calculated points: 33 from 33 - - points from cache: 10 from 26 + - points from cache: 0 from 33 - number of nodes: 4 @@ -93,11 +95,11 @@ ┌─────────────┬────────┬────────┬─────────┬─────────────┬──────────┬─────────┬────────┐ │ │ start │ min │ max │ sum of diff │ expected │ changes │ final │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ temperature │ 0.4533 │ 0.00 │ 1.00 │ 0.28 │ 0.01 │ 12 │ 0.9997 │ +│ temperature │ 0.1471 │ 0.00 │ 1.00 │ 8.73 │ 0.35 │ 17 │ 1.0000 │ │ decrease │ │ │ │ │ │ │ │ │ coefficient │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 54 │ 10.00 │ 200.00 │ 397.21 │ 20.91 │ 12 │ 120 │ +│ max │ 112 │ 10.00 │ 200.00 │ 188.84 │ 7.55 │ 17 │ 110 │ │ mutations │ │ │ │ │ │ │ │ │ per │ │ │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ @@ -111,14 +113,14 @@ │ elitism │ -0.00 │ - │ - │ - │ - │ - │ 0.00 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 91 │ 1.00 │ 100.00 │ 920.69 │ 48.46 │ 12 │ 87 │ +│ max │ 99 │ 1.00 │ 100.00 │ 1208.63 │ 48.35 │ 17 │ 100 │ │ stale │ │ │ │ │ │ │ │ │ iterations │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ │ population │ 1 │ 1.00 │ 1.00 │ 0.00 │ 0.00 │ 0 │ 1 │ │ size │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ dynasties │ 2849 │ 100.00 │ 5000.00 │ 35258.61 │ 1855.72 │ 12 │ 117 │ +│ dynasties │ 808 │ 100.00 │ 5000.00 │ 38996.81 │ 1559.87 │ 17 │ 123 │ │ limit │ │ │ │ │ │ │ │ └─────────────┴────────┴────────┴─────────┴─────────────┴──────────┴─────────┴────────┘ ``` @@ -130,7 +132,9 @@ - `max number of iterations` : limit of total iterations of optimization process, termination condition - `max no improvement iterations` : max amount of steps performed without detected improvement, termination condition - `improvement threshold` : minimal value detected as improvement in objective function result - - `calculated points` : new calculated points that were not found in cache + - `termination reason` : the reason why optimization process was stopped + - `iterations number` : actual number of iterations performed during optimization + - `resumed after stale` : how many times optimization progress was resumed after some iterations without improvement - `points from cache` : points calculated during previous optimizations and read from cache - `number of nodes` : number of nodes in graph representing cities from traveling salesman problem - `execution time` : duration of shortest found hybrid optimization process using final parameters, measured in seconds @@ -144,19 +148,19 @@ - `final` : calculated value of parameter for which execution time was the lowest ## For GA: - - max number of iterations: 100 + - max number of iterations: 50 - max no improvement iterations : 10 - improvement threshold : 0.005s - - calculated points: 40 from 67 + - calculated points: 51 from 58 - - points from cache: 27 from 67 + - points from cache: 7 from 58 - number of nodes: 4 - - execution time: 0.033s + - execution time: 0.036s - parameters: @@ -164,32 +168,32 @@ ┌─────────────┬────────┬────────┬─────────┬─────────────┬──────────┬─────────┬────────┐ │ │ start │ min │ max │ sum of diff │ expected │ changes │ final │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ temperature │ 0.9963 │ 0.00 │ 1.00 │ 0.05 │ 0.00 │ 35 │ 1.0000 │ +│ temperature │ 0.9963 │ 0.00 │ 1.00 │ 0.05 │ 0.00 │ 32 │ 1.0000 │ │ decrease │ │ │ │ │ │ │ │ │ coefficient │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 170 │ 10.00 │ 200.00 │ 4452.25 │ 71.81 │ 35 │ 18 │ +│ max │ 170 │ 10.00 │ 200.00 │ 2888.32 │ 53.49 │ 32 │ 24 │ │ mutations │ │ │ │ │ │ │ │ │ per │ │ │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ mutation │ 0.39 │ 0.10 │ 1.00 │ 7.29 │ 0.12 │ 35 │ 0.13 │ +│ mutation │ 0.39 │ 0.10 │ 1.00 │ 7.22 │ 0.13 │ 32 │ 0.10 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ crossover │ 0.81 │ 0.10 │ 1.00 │ 10.88 │ 0.18 │ 35 │ 0.29 │ +│ crossover │ 0.81 │ 0.10 │ 1.00 │ 8.82 │ 0.16 │ 32 │ 0.28 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ elitism │ -0.20 │ - │ - │ - │ - │ - │ 0.58 │ +│ elitism │ -0.20 │ - │ - │ - │ - │ - │ 0.61 │ │ rate │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ max │ 58 │ 1.00 │ 100.00 │ 1560.73 │ 25.17 │ 35 │ 28 │ +│ max │ 58 │ 1.00 │ 100.00 │ 1589.45 │ 29.43 │ 32 │ 100 │ │ stale │ │ │ │ │ │ │ │ │ iterations │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ population │ 572 │ 10.00 │ 2000.00 │ 44693.82 │ 720.87 │ 35 │ 19 │ +│ population │ 572 │ 10.00 │ 2000.00 │ 38470.00 │ 712.41 │ 32 │ 46 │ │ size │ │ │ │ │ │ │ │ ├─────────────┼────────┼────────┼─────────┼─────────────┼──────────┼─────────┼────────┤ -│ dynasties │ 1824 │ 100.00 │ 2000.00 │ 43273.64 │ 697.96 │ 35 │ 123 │ +│ dynasties │ 1824 │ 100.00 │ 2000.00 │ 34862.61 │ 645.60 │ 32 │ 115 │ │ limit │ │ │ │ │ │ │ │ └─────────────┴────────┴────────┴─────────┴─────────────┴──────────┴─────────┴────────┘ ``` @@ -201,7 +205,9 @@ - `max number of iterations` : limit of total iterations of optimization process, termination condition - `max no improvement iterations` : max amount of steps performed without detected improvement, termination condition - `improvement threshold` : minimal value detected as improvement in objective function result - - `calculated points` : new calculated points that were not found in cache + - `termination reason` : the reason why optimization process was stopped + - `iterations number` : actual number of iterations performed during optimization + - `resumed after stale` : how many times optimization progress was resumed after some iterations without improvement - `points from cache` : points calculated during previous optimizations and read from cache - `number of nodes` : number of nodes in graph representing cities from traveling salesman problem - `execution time` : duration of shortest found hybrid optimization process using final parameters, measured in seconds @@ -221,11 +227,11 @@ │ │ coefficient │ per │ │ │ │ iterations │ │ │ │ │ │ │ dynasty │ │ │ │ │ │ │ │ ├────────┼─────────────┼───────────┼──────────┼───────────┼─────────┼────────────┼────────────┼───────────┼───────────┤ -│ hybrid │ 0.7349 │ 33 │ 0.13 │ 0.86 │ 0.01 │ 62 │ 1 │ 138 │ 0.008s │ +│ hybrid │ 0.9999 │ 54 │ 0.02 │ 0.31 │ 0.66 │ 30 │ 10 │ 130 │ 0.018s │ ├────────┼─────────────┼───────────┼──────────┼───────────┼─────────┼────────────┼────────────┼───────────┼───────────┤ -│ SA │ 0.9997 │ 120 │ 1.00 │ 0.00 │ 0.00 │ 87 │ 1 │ 117 │ 0.007s │ +│ SA │ 1.0000 │ 110 │ 1.00 │ 0.00 │ 0.00 │ 100 │ 1 │ 123 │ 0.007s │ ├────────┼─────────────┼───────────┼──────────┼───────────┼─────────┼────────────┼────────────┼───────────┼───────────┤ -│ GA │ 1.0000 │ 18 │ 0.13 │ 0.29 │ 0.58 │ 28 │ 19 │ 123 │ 0.033s │ +│ GA │ 1.0000 │ 24 │ 0.10 │ 0.28 │ 0.61 │ 100 │ 46 │ 115 │ 0.036s │ └────────┴─────────────┴───────────┴──────────┴───────────┴─────────┴────────────┴────────────┴───────────┴───────────┘ ``` diff --git a/module/move/plot_interface/Readme.md b/module/move/plot_interface/Readme.md index fb3ea11556..0e604acfb8 100644 --- a/module/move/plot_interface/Readme.md +++ b/module/move/plot_interface/Readme.md @@ -2,8 +2,7 @@ # Module :: plot_interface - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml)[![docs.rs](https://img.shields.io/docsrs/plot_interface?color=e3e8f0&logo=docs.rs)](https://docs.rs/plot_interface)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fplot_interface_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20plot_interface_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_plot_interface_push.yml) [![docs.rs](https://img.shields.io/docsrs/plot_interface?color=e3e8f0&logo=docs.rs)](https://docs.rs/plot_interface) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Plot interface. diff --git a/module/move/refiner/Readme.md b/module/move/refiner/Readme.md index 949b13bc54..e6455c5205 100644 --- a/module/move/refiner/Readme.md +++ b/module/move/refiner/Readme.md @@ -2,8 +2,7 @@ # Module :: refiner - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml)[![docs.rs](https://img.shields.io/docsrs/refiner?color=e3e8f0&logo=docs.rs)](https://docs.rs/refiner)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Frefiner_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20refiner_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_refiner_push.yml) [![docs.rs](https://img.shields.io/docsrs/refiner?color=e3e8f0&logo=docs.rs)](https://docs.rs/refiner) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Utility to operate files from a command line. diff --git a/module/move/sqlx_query/Readme.md b/module/move/sqlx_query/Readme.md index 0f87657952..d576a121fa 100644 --- a/module/move/sqlx_query/Readme.md +++ b/module/move/sqlx_query/Readme.md @@ -2,8 +2,7 @@ # Module :: sqlx_query - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml)[![docs.rs](https://img.shields.io/docsrs/sqlx_query?color=e3e8f0&logo=docs.rs)](https://docs.rs/sqlx_query)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fsqlx_query_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20sqlx_query_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_sqlx_query_push.yml) [![docs.rs](https://img.shields.io/docsrs/sqlx_query?color=e3e8f0&logo=docs.rs)](https://docs.rs/sqlx_query) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) The tool to make CLI ( commands user interface ). It is able to aggregate external binary applications, as well as functions, which are written in your language. diff --git a/module/move/unitore/Cargo.toml b/module/move/unitore/Cargo.toml index 812ea26e02..8f98fba818 100644 --- a/module/move/unitore/Cargo.toml +++ b/module/move/unitore/Cargo.toml @@ -32,6 +32,7 @@ enabled = [] [dependencies] error_tools = { workspace = true, features = [ "default" ] } +proper_path_tools = { workspace = true, features = [ "default" ] } tokio = { version = "1.36.0", features = [ "rt", "rt-multi-thread", "io-std", "macros" ] } hyper = { version = "1.1.0", features = [ "client" ] } hyper-tls = "0.6.0" diff --git a/module/move/unitore/Readme.md b/module/move/unitore/Readme.md index 8683a61d36..e631beb82b 100644 --- a/module/move/unitore/Readme.md +++ b/module/move/unitore/Readme.md @@ -1,8 +1,7 @@ # Module :: unitore - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml)[![docs.rs](https://img.shields.io/docsrs/unitore?color=e3e8f0&logo=docs.rs)](https://docs.rs/unitore)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Funitore_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20unitore_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_unitore_push.yml) [![docs.rs](https://img.shields.io/docsrs/unitore?color=e3e8f0&logo=docs.rs)](https://docs.rs/unitore) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Feed reader with the ability to set updates frequency. @@ -48,7 +47,7 @@ cargo run .feeds.list ``` To get custom information about feeds or frames run SQL query to storage database using command `.query.execute` with query string: ```bash -cargo run .query.execute \'SELECT title, links, MIN\(published\) FROM frame\' +cargo run .query.execute 'SELECT title, links, MIN(published) FROM frame' ``` To remove config file from storage use command `.config.delete` with path to config file: ```bash diff --git a/module/move/unitore/src/Readme.md b/module/move/unitore/src/Readme.md new file mode 100644 index 0000000000..be6a15dfc1 --- /dev/null +++ b/module/move/unitore/src/Readme.md @@ -0,0 +1,6 @@ +## File structure + +- `command` - Contains commands for unitore cli. +- `action` - Contains functions that are executed when command are performed. +- `entity` - Contains entities that are used in commands execution. +- `tool` - Additional functions for convenient use of application. \ No newline at end of file diff --git a/module/move/unitore/src/executor/actions/config.rs b/module/move/unitore/src/action/config.rs similarity index 63% rename from module/move/unitore/src/executor/actions/config.rs rename to module/move/unitore/src/action/config.rs index 6a24392965..fae3bcf67d 100644 --- a/module/move/unitore/src/executor/actions/config.rs +++ b/module/move/unitore/src/action/config.rs @@ -1,25 +1,22 @@ -//! Actions and report for commands for config files. +//! Actions and report for config files. + +use std::path::PathBuf; use crate::*; -use super::*; -use error_tools::{ err, for_app::Context, BasicError, Result }; -use executor::FeedManager; -use storage:: +use error_tools::{ for_app::Context, Result }; +use sled_adapter::FeedStorage; +use entity:: { - FeedStorage, feed::{ FeedStore, Feed }, config::{ ConfigStore, Config }, }; +use action::Report; use gluesql::{ prelude::Payload, sled_storage::SledStorage }; /// Add configuration file with subscriptions to storage. -pub async fn add_config( storage : FeedStorage< SledStorage >, args : &wca::Args ) -> Result< impl Report > +pub async fn config_add( mut storage : FeedStorage< SledStorage >, path : &PathBuf ) -> Result< impl Report > { - let path : std::path::PathBuf = args - .get_owned::< wca::Value >( 0 ) - .ok_or_else::< BasicError, _ >( || err!( "Cannot get path argument for command .config.add" ) )? - .into() - ; + let path = proper_path_tools::path::normalize( path ); let mut err_str = format!( "Invalid path for config file {:?}", path ); @@ -37,54 +34,51 @@ pub async fn add_config( storage : FeedStorage< SledStorage >, args : &wca::Args err_str = format!( "Invalid path for config file {:?}", abs_path ); } } - let path = path.canonicalize().context( err_str )?; - let config = Config::new( path.to_string_lossy().to_string() ); - let mut manager = FeedManager::new( storage ); + if !path.exists() + { + return Err( error_tools::for_app::Error::msg( err_str ) ); + } + + let abs_path = path.canonicalize()?; + let config = Config::new( abs_path.to_string_lossy().to_string() ); - let config_report = manager.storage - .add_config( &config ) + let config_report = storage + .config_add( &config ) .await .context( "Added 0 config files.\n Failed to add config file to storage." )? ; let feeds = feed_config::read( config.path() )? .into_iter() - .map( | feed | Feed::new( feed.link, feed.update_period ) ) + .map( | feed | Feed::new( feed.link, feed.update_period, config.path() ) ) .collect::< Vec< _ > >() ; - let new_feeds = manager.storage.save_feeds( feeds ).await?; + let new_feeds = storage.feeds_save( feeds ).await?; Ok( ConfigReport{ payload : config_report, new_feeds : Some( new_feeds ) } ) } /// Remove configuration file from storage. -pub async fn delete_config( storage : FeedStorage< SledStorage >, args : &wca::Args ) -> Result< impl Report > +pub async fn config_delete( mut storage : FeedStorage< SledStorage >, path : &PathBuf ) -> Result< impl Report > { - let path : std::path::PathBuf = args - .get_owned::< wca::Value >( 0 ) - .ok_or_else::< BasicError, _ >( || err!( "Cannot get path argument for command .config.delete" ) )? - .into() - ; - + let path = proper_path_tools::path::normalize( path ); let path = path.canonicalize().context( format!( "Invalid path for config file {:?}", path ) )?; let config = Config::new( path.to_string_lossy().to_string() ); - let mut manager = FeedManager::new( storage ); Ok( ConfigReport::new( - manager.storage - .delete_config( &config ) + storage + .config_delete( &config ) .await .context( "Failed to remove config from storage." )? ) ) } /// List all files with subscriptions that are currently in storage. -pub async fn list_configs( storage : FeedStorage< SledStorage >, _args : &wca::Args ) -> Result< impl Report > +pub async fn config_list( mut storage : FeedStorage< SledStorage >, _args : &wca::Args ) -> Result< impl Report > { - let mut manager = FeedManager::new( storage ); - Ok( ConfigReport::new( manager.storage.list_configs().await? ) ) + Ok( ConfigReport::new( storage.config_list().await? ) ) } /// Information about result of command for subscription config. @@ -106,7 +100,7 @@ impl ConfigReport impl std::fmt::Display for ConfigReport { - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result { const EMPTY_CELL : &'static str = ""; @@ -117,7 +111,7 @@ impl std::fmt::Display for ConfigReport writeln!( f, "Added {} config file(s)", number )?; writeln!( f, - "Added {} feeds", + "Added {} feed(s)", self.new_feeds .as_ref() .and_then( | payload | @@ -140,7 +134,7 @@ impl std::fmt::Display for ConfigReport rows.push( vec![ EMPTY_CELL.to_owned(), String::from( row[ 0 ].clone() ) ] ); } - let table = table_display::plain_table( rows ); + let table = tool::table_display::plain_table( rows ); if let Some( table ) = table { write!( f, "{}", table )?; diff --git a/module/move/unitore/src/executor/actions/feed.rs b/module/move/unitore/src/action/feed.rs similarity index 66% rename from module/move/unitore/src/executor/actions/feed.rs rename to module/move/unitore/src/action/feed.rs index 850384c846..70063de854 100644 --- a/module/move/unitore/src/executor/actions/feed.rs +++ b/module/move/unitore/src/action/feed.rs @@ -1,22 +1,15 @@ -//! Endpoints and report for feed commands. +//! Feed actions and reports. use crate::*; -use executor:: -{ - FeedManager, - actions::{ Report, frame::SelectedEntries }, -}; -use storage::{ FeedStorage, feed::FeedStore }; +use action::{ Report, frame::SelectedEntries }; +use sled_adapter::FeedStorage; +use entity::feed::FeedStore; use error_tools::Result; -/// List all feeds. -pub async fn list_feeds( - storage : FeedStorage< gluesql::sled_storage::SledStorage >, - _args : &wca::Args, -) -> Result< impl Report > +/// List all feeds from storage. +pub async fn feeds_list( mut storage : FeedStorage< gluesql::sled_storage::SledStorage > ) -> Result< impl Report > { - let mut manager = FeedManager::new( storage ); - manager.storage.get_all_feeds().await + storage.feeds_list().await } const EMPTY_CELL : &'static str = ""; @@ -36,7 +29,7 @@ impl FeedsReport impl std::fmt::Display for FeedsReport { - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result { writeln!( f, "Selected feeds:" )?; if !self.0.selected_rows.is_empty() @@ -51,7 +44,7 @@ impl std::fmt::Display for FeedsReport let mut headers = vec![ EMPTY_CELL.to_owned() ]; headers.extend( self.0.selected_columns.iter().map( | str | str.to_owned() ) ); - let table = table_display::table_with_headers( headers, rows ); + let table = tool::table_display::table_with_headers( headers, rows ); if let Some( table ) = table { write!( f, "{}", table )?; diff --git a/module/move/unitore/src/executor/actions/frame.rs b/module/move/unitore/src/action/frame.rs similarity index 77% rename from module/move/unitore/src/executor/actions/frame.rs rename to module/move/unitore/src/action/frame.rs index b6e3e40d8b..2acb4cd168 100644 --- a/module/move/unitore/src/executor/actions/frame.rs +++ b/module/move/unitore/src/action/frame.rs @@ -1,42 +1,33 @@ -//! Frames commands actions. +//! Frames actions and reports. use crate::*; -use super::*; -use executor::FeedManager; -use storage:: +use sled_adapter::FeedStorage; +use entity:: { - FeedStorage, feed::FeedStore, config::ConfigStore, - frame::{ FrameStore, RowValue } + frame::{ FrameStore, CellValue } }; use gluesql::prelude::{ Payload, Value, SledStorage }; use feed_config; use error_tools::{ err, Result }; +use action::Report; // qqq : review the whole project and make sure all names are consitant: actions, commands, its tests /// List all frames. -pub async fn list_frames -( - storage : FeedStorage< SledStorage >, - _args : &wca::Args, -) -> Result< impl Report > +pub async fn frames_list( mut storage : FeedStorage< SledStorage > ) -> Result< impl Report > { - let mut manager = FeedManager::new( storage ); - manager.storage.list_frames().await + storage.frames_list().await } /// Update all frames from config files saved in storage. -pub async fn download_frames +pub async fn frames_download ( - storage : FeedStorage< SledStorage >, - _args : &wca::Args, + mut storage : FeedStorage< SledStorage > ) -> Result< impl Report > { - let mut manager = FeedManager::new( storage ); - let payload = manager.storage.list_configs().await?; - + let payload = storage.config_list().await?; let configs = match &payload { Payload::Select { labels: _, rows: rows_vec } => @@ -71,12 +62,12 @@ pub async fn download_frames let mut feeds = Vec::new(); let client = retriever::FeedClient; - for i in 0..subscriptions.len() + for subscription in subscriptions { - let feed = retriever::FeedFetch::fetch(&client, subscriptions[ i ].link.clone()).await?; - feeds.push( ( feed, subscriptions[ i ].update_period.clone(), subscriptions[ i ].link.clone() ) ); + let feed = client.fetch( subscription.link.clone() ).await?; + feeds.push( ( feed, subscription.update_period.clone(), subscription.link ) ); } - manager.storage.process_feeds( feeds ).await + storage.feeds_process( feeds ).await } @@ -120,10 +111,10 @@ impl FramesReport impl std::fmt::Display for FramesReport { - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result { let initial = vec![ vec![ format!( "Feed title: {}", self.feed_link ) ] ]; - let table = table_display::table_with_headers( initial[ 0 ].clone(), Vec::new() ); + let table = tool::table_display::table_with_headers( initial[ 0 ].clone(), Vec::new() ); if let Some( table ) = table { write!( f, "{}", table )?; @@ -133,7 +124,7 @@ impl std::fmt::Display for FramesReport [ vec![ EMPTY_CELL.to_owned(), format!( "Updated frames: {}", self.updated_frames ) ], vec![ EMPTY_CELL.to_owned(), format!( "Inserted frames: {}", self.new_frames ) ], - vec![ EMPTY_CELL.to_owned(), format!( "Number of frames in storage: {}", self.existing_frames ) ], + vec![ EMPTY_CELL.to_owned(), format!( "Number of frames in storage: {}", self.existing_frames + self.new_frames ) ], ]; if !self.selected_frames.selected_columns.is_empty() @@ -141,7 +132,7 @@ impl std::fmt::Display for FramesReport rows.push( vec![ EMPTY_CELL.to_owned(), format!( "Selected frames:" ) ] ); } - let table = table_display::plain_table( rows ); + let table = tool::table_display::plain_table( rows ); if let Some( table ) = table { write!( f, "{}", table )?; @@ -149,8 +140,14 @@ impl std::fmt::Display for FramesReport for frame in &self.selected_frames.selected_rows { + let first_row = vec! + [ + INDENT_CELL.to_owned(), + self.selected_frames.selected_columns[ 0 ].clone(), + textwrap::fill( &String::from( frame[ 0 ].clone() ), 120 ), + ]; let mut rows = Vec::new(); - for i in 0..self.selected_frames.selected_columns.len() + for i in 1..self.selected_frames.selected_columns.len() { let inner_row = vec! [ @@ -161,7 +158,7 @@ impl std::fmt::Display for FramesReport rows.push( inner_row ); } - let table = table_display::plain_table( rows ); + let table = tool::table_display::table_with_headers( first_row, rows ); if let Some( table ) = table { writeln!( f, "{}", table )?; @@ -174,7 +171,7 @@ impl std::fmt::Display for FramesReport impl Report for FramesReport {} -/// Items get from select query from storage. +/// Items retrieved by select queries from storage. #[ derive( Debug ) ] pub struct SelectedEntries { @@ -195,7 +192,7 @@ impl SelectedEntries impl std::fmt::Display for SelectedEntries { - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result { if !self.selected_columns.is_empty() { @@ -203,7 +200,7 @@ impl std::fmt::Display for SelectedEntries { for i in 0..self.selected_columns.len() { - write!( f, "{} : {}, ", self.selected_columns[ i ], RowValue( &row[ i ] ) )?; + write!( f, "{} : {}, ", self.selected_columns[ i ], CellValue( &row[ i ] ) )?; } writeln!( f, "" )?; } @@ -219,7 +216,7 @@ pub struct UpdateReport( pub Vec< FramesReport > ); impl std::fmt::Display for UpdateReport { - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result { for report in &self.0 { @@ -247,7 +244,7 @@ pub struct ListReport( pub Vec< FramesReport > ); impl std::fmt::Display for ListReport { - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result { for report in &self.0 { diff --git a/module/move/unitore/src/executor/actions/mod.rs b/module/move/unitore/src/action/mod.rs similarity index 85% rename from module/move/unitore/src/executor/actions/mod.rs rename to module/move/unitore/src/action/mod.rs index 80471e3650..5f958526f9 100644 --- a/module/move/unitore/src/executor/actions/mod.rs +++ b/module/move/unitore/src/action/mod.rs @@ -8,6 +8,8 @@ // entity - with all entities // tool - with something not directly related to the problem, but convenient to have as a separate function/structure +// aaa: added folders + pub mod frame; pub mod feed; pub mod config; @@ -15,7 +17,8 @@ pub mod query; pub mod table; // qqq : what is it for? purpose? -/// General report. +// aaa : added explanation +/// General report trait for commands return type. pub trait Report : std::fmt::Display + std::fmt::Debug { /// Print report of executed command. diff --git a/module/move/unitore/src/executor/actions/query.rs b/module/move/unitore/src/action/query.rs similarity index 78% rename from module/move/unitore/src/executor/actions/query.rs rename to module/move/unitore/src/action/query.rs index 84064075a7..d9f4180384 100644 --- a/module/move/unitore/src/executor/actions/query.rs +++ b/module/move/unitore/src/action/query.rs @@ -1,28 +1,21 @@ -//! Query command endpoint and report. +//! Query actions and report. // qqq : don't use both +// aaa : fixed use crate::*; -use super::*; use gluesql::core::executor::Payload; -use storage::{ FeedStorage, Store }; -use executor::FeedManager; -use error_tools::{ err, BasicError, Result }; +use sled_adapter::Store; +use action::Report; +use error_tools::Result; /// Execute query specified in query string. -pub async fn execute_query +pub async fn query_execute ( - storage : FeedStorage< gluesql::sled_storage::SledStorage >, - args : &wca::Args, + mut storage : impl Store, + query_str : String, ) -> Result< impl Report > { - let query = args - .get_owned::< Vec::< String > >( 0 ) - .ok_or_else::< BasicError, _ >( || err!( "Cannot get Query argument for command .query.execute" ) )? - .join( " " ) - ; - - let mut manager = FeedManager::new( storage ); - manager.storage.execute_query( query ).await + storage.query_execute( query_str ).await } const EMPTY_CELL : &'static str = ""; @@ -68,7 +61,7 @@ impl std::fmt::Display for QueryReport ]; rows.push( new_row ); } - let table = table_display::plain_table( rows ); + let table = tool::table_display::plain_table( rows ); if let Some( table ) = table { writeln!( f, "{}", table )?; @@ -91,3 +84,4 @@ impl Report for QueryReport {} // qqq : good tests for query action // all tables should be touched by these tests +// aaa : added in https://github.com/Wandalen/wTools/pull/1284 diff --git a/module/move/unitore/src/action/table.rs b/module/move/unitore/src/action/table.rs new file mode 100644 index 0000000000..ce9a4e756f --- /dev/null +++ b/module/move/unitore/src/action/table.rs @@ -0,0 +1,418 @@ +//! Tables metadata actions and reports. + +use crate::*; +use gluesql::prelude::Payload; +use std::collections::HashMap; +use action::Report; +use sled_adapter::FeedStorage; +use entity::table::TableStore; +use error_tools::Result; + +/// Get labels of column for specified table. +pub async fn table_list +( + mut storage : FeedStorage< gluesql::sled_storage::SledStorage >, + table_name : Option< String >, +) -> Result< impl Report > +{ + let mut table_names = Vec::new(); + if let Some( name ) = table_name + { + table_names.push( name ); + } + else + { + let tables = storage.tables_list().await?; + + let names = tables.0.keys().map( | k | k.clone() ).collect::< Vec< _ > >(); + table_names.extend( names.into_iter() ); + } + + let mut reports = Vec::new(); + for table_name in table_names + { + let result = storage.table_list( table_name.clone() ).await?; + + let mut table_description = String::new(); + let mut columns = HashMap::new(); + if let Payload::Select { labels: _label_vec, rows: rows_vec } = &result[ 0 ] + { + for row in rows_vec + { + let table = String::from( row[ 0 ].clone() ); + columns.entry( table ) + .and_modify( | vec : &mut Vec< String > | vec.push( String::from( row[ 1 ].clone() ) ) ) + .or_insert( vec![ String::from( row[ 1 ].clone() ) ] ) + ; + } + } + let mut columns_desc = HashMap::new(); + match table_name.as_str() + { + "feed" => + { + table_description = String::from( "Contains feed items." ); + + for label in columns.get( "feed" ).unwrap() + { + match label.as_str() + { + "link" => { columns_desc.insert + ( + label.clone(), + String::from( "Link to feed source, unique identifier for the feed" ), + ); } + "title" => { columns_desc.insert( label.clone(), String::from( "The title of the feed" ) ); } + "updated" => + { + columns_desc.insert( label.clone(), String::from + ( + "The time at which the feed was last modified. If not provided in the source, or invalid, is Null." + ) ); + }, + "type" => { columns_desc.insert( label.clone(), String::from( "Type of this feed (e.g. RSS2, Atom etc)" ) ); } + "authors" => { columns_desc.insert + ( + label.clone(), + String::from( "Collection of authors defined at the feed level" ) + ); } + "description" => { columns_desc.insert( label.clone(), String::from( "Description of the feed" ) ); } + "published" => { columns_desc.insert + ( + label.clone(), + String::from( "The publication date for the content in the channel" ), + ); } + "update_period" => { columns_desc.insert( label.clone(), String::from( "How often this feed must be updated" ) ); } + _ => { columns_desc.insert( label.clone(), String::from( "Desciption for this column hasn't been added yet!" ) ); } + } + } + }, + "frame" => + { + table_description = String::from( "Contains frame items." ); + for label in columns.get( "frame" ).unwrap() + { + match label.as_str() + { + "id" => + { + columns_desc.insert + ( + label.clone(), + String::from( "A unique identifier for this frame in the feed. " ), + ); + }, + "title" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Title of the frame" ), + ); + }, + "updated" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Time at which this item was fetched from source." ), + ); + }, + "authors" => + { + columns_desc.insert + ( + label.clone(), + String::from( "List of authors of the frame, optional." ) + ); + }, + "content" => + { + columns_desc.insert + ( + label.clone(), + String::from( "The content of the frame in html or plain text, optional." ), + ); + }, + "links" => + { + columns_desc.insert + ( + label.clone(), + String::from( "List of links associated with this item of related Web page and attachments." ), + ); + }, + "summary" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Short summary, abstract, or excerpt of the frame item, optional." ), + ); + }, + "categories" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Specifies a list of categories that the item belongs to." ), + ); + }, + "published" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Time at which this item was first published or updated." ), + ); + }, + "source" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Specifies the source feed if the frame was copied from one feed into another feed, optional." ), + ); + }, + "rights" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Conveys information about copyrights over the feed, optional." ), + ); + }, + "media" => + { + columns_desc.insert + ( + label.clone(), + String::from( "List of media oblects, encountered in the frame, optional." ), + ); + }, + "language" => + { + columns_desc.insert + ( + label.clone(), + String::from( "The language specified on the item, optional." ), + ); + }, + "feed_link" => + { + columns_desc.insert + ( + label.clone(), + String::from( "Link of feed that contains this frame." ), + ); + }, + _ => { columns_desc.insert( label.clone(), String::from( "Desciption for this column hasn't been added yet!" ) ); } + } + } + } + "config" => + { + table_description = String::from( "Contains paths to feed config files." ); + for label in columns.get( "config" ).unwrap() + { + match label.as_str() + { + "path" => { columns_desc.insert( label.clone(), String::from( "Path to configuration file" ) ); } + _ => { columns_desc.insert( label.clone(), String::from( "Desciption for this column hasn't been added yet!" ) ); } + } + } + }, + _ => {}, + } + + reports.push( ColumnsReport::new( table_name, table_description, columns_desc ) ); + } + + Ok( TablesColumnsReport( reports ) ) +} + +/// Get information about tables in storage. +pub async fn tables_list( mut storage : FeedStorage< gluesql::sled_storage::SledStorage > ) -> Result< impl Report > +{ + storage.tables_list().await +} + +const EMPTY_CELL : &'static str = ""; + +/// Information about execution of table columns commands. +#[ derive( Debug ) ] +pub struct TablesColumnsReport( pub Vec< ColumnsReport > ); + +impl std::fmt::Display for TablesColumnsReport +{ + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + { + for report in &self.0 + { + writeln!( f, "{}", report )?; + } + + Ok( () ) + } +} + +impl Report for TablesColumnsReport {} + +/// Information about execution of columns listing action. +#[ derive( Debug ) ] +pub struct ColumnsReport +{ + table_name : String, + table_description : String, + columns : std::collections::HashMap< String, String > +} + +impl ColumnsReport +{ + /// Create new table columns report. + pub fn new( table_name : String, table_description : String, columns : HashMap< String, String > ) -> Self + { + Self + { + table_name, + table_description, + columns, + } + } +} + +impl std::fmt::Display for ColumnsReport +{ + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + { + writeln!( f, "Table name: {}", self.table_name )?; + writeln!( f, "Description: {}", self.table_description )?; + + if !self.columns.is_empty() + { + writeln!( f, "Columns:" )?; + let mut rows = Vec::new(); + for ( label, desc ) in &self.columns + { + rows.push + ( + vec! + [ + EMPTY_CELL.to_owned(), + label.clone(), + desc.clone(), + ] + ); + } + let table = tool::table_display::table_with_headers + ( + vec! + [ + EMPTY_CELL.to_owned(), + "label".to_owned(), + "description".to_owned(), + ], + rows, + ); + + if let Some( table ) = table + { + writeln!( f, "{}", table )?; + } + } + else + { + writeln!( f, "No columns" )?; + } + + Ok( () ) + } +} + +impl Report for ColumnsReport {} + +/// Information about execution of tables commands. +/// Contains tables name, description and list of columns. +#[ derive( Debug ) ] +pub struct TablesReport( pub HashMap< String, ( String, Vec< String > ) > ); + +impl TablesReport +{ + /// Create new report from payload. + pub fn new( payload : Vec< Payload > ) -> Self + { + let mut result = std::collections::HashMap::new(); + if let Payload::Select { labels: _label_vec, rows: rows_vec } = &payload[ 0 ] + { + { + for row in rows_vec + { + let table = String::from( row[ 0 ].clone() ); + let table_description = match table.as_str() + { + "feed" => String::from( "Contains feed items." ), + "frame" => String::from( "Contains frame items." ), + "config" => String::from( "Contains paths to feed config files." ), + _ => String::new(), + }; + result.entry( table ) + .and_modify( | ( _, vec ) : &mut ( String, Vec< String > ) | vec.push( String::from( row[ 1 ].clone() ) ) ) + .or_insert( ( table_description, vec![ String::from( row[ 1 ].clone() ) ] ) ) + ; + } + } + } + TablesReport( result ) + } +} + +impl std::fmt::Display for TablesReport +{ + fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + { + writeln!( f, "Storage tables:" )?; + let mut rows = Vec::new(); + for ( table_name, ( desc, columns ) ) in &self.0 + { + let columns_str = if !columns.is_empty() + { + format!( "{};", columns.join( ", " ) ) + } + else + { + String::from( "No columns" ) + }; + + rows.push + ( + vec! + [ + EMPTY_CELL.to_owned(), + table_name.to_owned(), + textwrap::fill( desc, 80 ), + textwrap::fill( &columns_str, 80 ), + ] + ); + } + + let table = tool::table_display::table_with_headers + ( + vec! + [ + EMPTY_CELL.to_owned(), + "name".to_owned(), + "description".to_owned(), + "columns".to_owned(), + ], + rows, + ); + if let Some( table ) = table + { + writeln!( f, "{}", table )?; + } + + Ok( () ) + } +} + +impl Report for TablesReport {} diff --git a/module/move/unitore/src/command/config.rs b/module/move/unitore/src/command/config.rs new file mode 100644 index 0000000000..72eb063007 --- /dev/null +++ b/module/move/unitore/src/command/config.rs @@ -0,0 +1,165 @@ +//! Config files commands. + +use std::path::PathBuf; + +use crate::*; +use gluesql::sled_storage::sled::Config; +use wca::{ Command, Type, VerifiedCommand }; +use sled_adapter::FeedStorage; +use action::{ Report, config::{ config_add, config_delete, config_list } }; +use error_tools::Result; + +/// Struct that provides commands for config files. +#[ derive( Debug ) ] +pub struct ConfigCommand; + +impl ConfigCommand +{ + /// Create command for adding config. + pub fn add() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok + ( + Command::former() + .phrase( "config.add" ) + .long_hint( concat! + ( + "Add file with feeds configurations. Subject: path to config file.\n", + " Example: .config.add ./config/feeds.toml\n", + " The file should contain config entities with fields:\n", + " - `update_period` : update frequency for feed. Example values: `12h`, `1h 20min`, `2days 5h`;\n", + " - `link` : URL for feed source;\n\n", + " Example:\n", + " [[config]]\n", + " update_period = \"1min\"\n", + " link = \"https://feeds.bbci.co.uk/news/world/rss.xml\"\n", + )) + .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() + .routine( move | o : VerifiedCommand | + { + let path_arg = o.args + .get_owned::< wca::Value >( 0 ); + + if let Some( path ) = path_arg + { + let path : PathBuf = path.into(); + + let res = rt.block_on + ( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + config_add( feed_storage, &path ).await + } + ); + + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + } + }) + .end() + ) + } + + /// Create command for deleting config. + pub fn delete() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok( + Command::former() + .phrase( "config.delete" ) + .long_hint( concat! + ( + "Delete file with feeds configuraiton. Subject: path to config file.\n", + " Example: .config.delete ./config/feeds.toml", + )) + .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() + .routine( move | o : VerifiedCommand | + { + let path_arg = o.args + .get_owned::< wca::Value >( 0 ); + + if let Some( path ) = path_arg + { + let path : PathBuf = path.into(); + + let res = rt.block_on + ( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + config_delete( feed_storage, &path ).await + } + ); + + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + } + }) + .end() + ) + } + + /// Create command for listing all config files in storage. + pub fn list() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok + ( + Command::former() + .phrase( "config.list" ) + .long_hint( concat! + ( + "List all config files saved in storage.\n", + " Example: .config.list", + )) + .routine( move | o : VerifiedCommand | + { + let res = rt.block_on( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + config_list( feed_storage, &o.args ).await + }); + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + + }) + .end() + ) + } +} diff --git a/module/move/unitore/src/command/feed.rs b/module/move/unitore/src/command/feed.rs new file mode 100644 index 0000000000..148d404952 --- /dev/null +++ b/module/move/unitore/src/command/feed.rs @@ -0,0 +1,55 @@ +//! Feed command. + +use crate::*; +use gluesql::sled_storage::sled::Config; +use wca::{ Command, VerifiedCommand }; +use sled_adapter::FeedStorage; +use action::{ Report, feed::feeds_list }; +use error_tools::Result; + +/// Struct that provides commands for feed. +#[ derive( Debug ) ] +pub struct FeedCommand; + +impl FeedCommand +{ + /// Create command that lists all feeds in storage. + pub fn list() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok + ( + Command::former() + .phrase( "feeds.list" ) + .long_hint( concat! + ( + "List all feeds from storage.\n", + " Example: .feeds.list", + )) + .routine( move | _o : VerifiedCommand | + { + let res = rt.block_on( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + feeds_list( feed_storage ).await + }); + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + + }) + .end() + ) + } +} \ No newline at end of file diff --git a/module/move/unitore/src/command/frame.rs b/module/move/unitore/src/command/frame.rs new file mode 100644 index 0000000000..8a4f18a756 --- /dev/null +++ b/module/move/unitore/src/command/frame.rs @@ -0,0 +1,93 @@ +//! Frame commands. + +use crate::*; +use gluesql::sled_storage::sled::Config; +use wca::{ Command, VerifiedCommand }; +use sled_adapter::FeedStorage; +use action::{ Report, frame::{ frames_list, frames_download } }; +use error_tools::Result; + +/// Struct that provides commands for frames. +#[ derive( Debug ) ] +pub struct FrameCommand; + +impl FrameCommand +{ + /// Create command that lists all frames in storage. + pub fn list() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok + ( + Command::former() + .phrase( "frames.list" ) + .long_hint( concat! + ( + "List all frames saved in storage.\n", + " Example: .frames.list", + )) + .routine( move | _o : VerifiedCommand | + { + let res = rt.block_on( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + frames_list( feed_storage ).await + }); + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + + }) + .end() + ) + } + + /// Creates command that downloads frames from feeds specified in config files. + pub fn download() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok( + Command::former() + .phrase( "frames.download" ) + .hint( "Download frames from feed sources provided in config files." ) + .long_hint(concat! + ( + "Download frames from feed sources provided in config files.\n", + " Example: .frames.download", + )) + .routine( move | _o : VerifiedCommand | + { + let res = rt.block_on( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + frames_download( feed_storage ).await + }); + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + }) + .end() ) + } +} \ No newline at end of file diff --git a/module/move/unitore/src/command/mod.rs b/module/move/unitore/src/command/mod.rs new file mode 100644 index 0000000000..5548eb2f17 --- /dev/null +++ b/module/move/unitore/src/command/mod.rs @@ -0,0 +1,7 @@ +//! Commands for unitore executor. + +pub mod table; +pub mod frame; +pub mod feed; +pub mod query; +pub mod config; \ No newline at end of file diff --git a/module/move/unitore/src/command/query.rs b/module/move/unitore/src/command/query.rs new file mode 100644 index 0000000000..24519e1a86 --- /dev/null +++ b/module/move/unitore/src/command/query.rs @@ -0,0 +1,71 @@ +//! Query command. + +use crate::*; +use gluesql::sled_storage::sled::Config; +use wca::{ Command, Type, VerifiedCommand }; +use sled_adapter::FeedStorage; +use action::{ Report, query::query_execute }; +use error_tools::Result; + +/// Struct that provides commands for queries. +#[ derive( Debug ) ] +pub struct QueryCommand; + +impl QueryCommand +{ + /// Creates command for custom query execution. + pub fn execute() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok + ( + Command::former() + .phrase( "query.execute" ) + .long_hint( concat! + ( + "Execute custom query. Subject: query string.\n", + " Example query:\n", + " - select all frames:\n", + r#" .query.execute 'SELECT * FROM frame'"#, + "\n", + " - select title and link to the most recent frame:\n", + r#" .query.execute 'SELECT title, links, MIN( published ) FROM frame'"#, + "\n\n", + )) + .subject().hint( "Query" ).kind( Type::String ).optional( false ).end() + .routine( move | o : VerifiedCommand | + { + let query_arg = o.args + .get_owned::< String >( 0 ) + ; + + if let Some( query_str ) = query_arg + { + let res = rt.block_on + ( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + query_execute( feed_storage, query_str ).await + } + ); + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + } + + }) + .end() + ) + } +} \ No newline at end of file diff --git a/module/move/unitore/src/command/table.rs b/module/move/unitore/src/command/table.rs new file mode 100644 index 0000000000..67c82f23a0 --- /dev/null +++ b/module/move/unitore/src/command/table.rs @@ -0,0 +1,105 @@ +//! Table and columns commands. + +use crate::*; +use gluesql::sled_storage::sled::Config; +use wca::{ Command, Type, VerifiedCommand }; +use sled_adapter::FeedStorage; +use action::{ Report, table::{ table_list, tables_list } }; +use error_tools::Result; + +/// Struct that provides commands for table information. +#[ derive( Debug ) ] +pub struct TableCommand; + +impl TableCommand +{ + /// Creates command to list info about tables in storage. + pub fn list() -> Result< Command > + { + let rt = tokio::runtime::Runtime::new()?; + + Ok + ( + Command::former() + .phrase( "table.list" ) + .long_hint( concat! + ( + "Delete file with feeds configuraiton. Subject: path to config file.\n", + " Example: .config.delete ./config/feeds.toml", + )) + .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() + .routine( move | o : VerifiedCommand | + { + let table_name_arg = o.args.get_owned::< String >( 0 ); + + let res = rt.block_on( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + table_list( feed_storage, table_name_arg ).await + } ); + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + }) + .end() + ) + } +} + +/// Struct that provides commands for table columns information. +#[ derive( Debug ) ] +pub struct TablesCommand; + +impl TablesCommand +{ + /// Creates command to list info about table columns in storage. + pub fn list() -> Result< Command > + { + + let rt = tokio::runtime::Runtime::new()?; + + Ok + ( + Command::former() + .phrase( "tables.list" ) + .long_hint( concat! + ( + "Delete file with feeds configuraiton. Subject: path to config file.\n", + " Example: .config.delete ./config/feeds.toml", + )) + .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() + .routine( move | _o : VerifiedCommand | + { + let res = rt.block_on( async move + { + let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) + .unwrap_or( String::from( "./_data" ) ) + ; + + let config = Config::default() + .path( path_to_storage ) + ; + + let feed_storage = FeedStorage::init_storage( &config ).await?; + tables_list( feed_storage ).await + } ); + match res + { + Ok( report ) => report.report(), + Err( err ) => println!( "{:?}", err ), + } + }) + .end() + ) + } +} \ No newline at end of file diff --git a/module/move/unitore/src/entity/config.rs b/module/move/unitore/src/entity/config.rs new file mode 100644 index 0000000000..b28a90a1dc --- /dev/null +++ b/module/move/unitore/src/entity/config.rs @@ -0,0 +1,56 @@ +//! Functionality for storing and retrieving config files. + +use error_tools::Result; +use gluesql::core::executor::Payload; + +/// Config file path. +#[ derive( Debug ) ] +pub struct Config( pub String ); + +impl Config +{ + /// Create new config with provided path. + pub fn new( path : String ) -> Self + { + Self( path ) + } + + /// Get path of config file. + pub fn path( &self ) -> String + { + self.0.clone() + } +} + +/// Functionality of config storing. +#[ async_trait::async_trait( ?Send ) ] +pub trait ConfigStore +{ + /// Add subscription. + async fn config_add( &mut self, config : &Config ) -> Result< Payload >; + + /// Remove subscription. + async fn config_delete( &mut self, config : &Config ) -> Result< Payload >; + + /// List subscriptions. + async fn config_list( &mut self ) -> Result< Payload >; +} + +// qqq : port and adapters should not be in the same file +// Ideally, they should be in different crates, but you should at least put them in different folders +// there should be a `sled_adapter`` folder +// aaa : moved to separate folder + + +// qqq : use AbsolutePath newtype from `path_tools` +// qqq : normalize all paths with `path_tools::path::normalize` +// https://docs.rs/proper_path_tools/latest/proper_path_tools/path/fn.normalize.html +// added path normalization + +// unitore .query.execute \'SELECT \* FROM feed\' +// qqq : something is broken in this table. also lack of association with config files +// aaa : added association with config + +// unitore .query.execute \'SELECT \* FROM x\' +// qqq : it is not obvious where one record ends and another begins +// aaa : added id highlight diff --git a/module/move/unitore/src/entity/feed.rs b/module/move/unitore/src/entity/feed.rs new file mode 100644 index 0000000000..7084e841dd --- /dev/null +++ b/module/move/unitore/src/entity/feed.rs @@ -0,0 +1,106 @@ +//! Feed storage entity and storage functions. + +use crate::*; +use std::time::Duration; +use error_tools::Result; +use gluesql::core:: +{ + ast_builder::{ null, text, timestamp, ExprNode }, + executor::Payload, + chrono::{ Utc, DateTime, SecondsFormat }, +}; + +use action:: +{ + feed::FeedsReport, + frame::UpdateReport, +}; + +/// Feed item. +#[ derive( Debug ) ] +pub struct Feed +{ + /// Link to feed source. + pub link : url::Url, + /// Title of feed. + pub title : Option< String >, + /// Last time the feed was fetched. + pub updated : Option< DateTime< Utc > >, + /// Authors of feed. + pub authors : Option< String >, + /// Short description of feed content. + pub description : Option< String >, + /// Date and time when feed was published. + pub published : Option< DateTime< Utc > >, + /// How often the feed frames must be fetched. + pub update_period : Duration, + /// Path to config file, from which this feed was saved. + pub config_file : String, +} + +impl Feed +{ + /// Create new feed item from source url and update period. + pub fn new( link : url::Url, update_period : Duration, config: String ) -> Self + { + Self + { + link, + title : None, + updated : None, + authors : None, + description : None, + published : None, + update_period, + config_file : config, + } + } +} + +/// Functionality of feed storage. +#[ mockall::automock ] +#[ async_trait::async_trait( ?Send ) ] +pub trait FeedStore +{ + /// Save new feeds to storage. + /// New feeds from config files that doesn't exist in storage will be inserted into `feed` table. + async fn feeds_save( &mut self, feeds : Vec< Feed > ) -> Result< Payload >; + + /// Update existing feeds in storage with new information. + /// Feed is updated one time during first fetch. + async fn feeds_update( &mut self, feed : Vec< Feed > ) -> Result< () >; + + /// Process new fetched feeds and frames. + /// Frames from recent fetch will be sorted into three categories: + /// - new items that will be inserted into `frame` table; + /// - modified items that will be updated; + /// - unchanged frames saved from previous fetches will be ignored. + async fn feeds_process( &mut self, feeds : Vec< ( feed_rs::model::Feed, Duration, url::Url ) > ) -> Result< UpdateReport >; + + /// Get existing feeds from storage. + /// Retrieves all feeds from `feed` table in storage. + async fn feeds_list( &mut self ) -> Result< FeedsReport >; +} +// qqq : poor description and probably naming. improve, please +// aaa : updated description + + +/// Get convenient format of frame item for using with GlueSQL expression builder. +/// Converts from Feed struct into vec of GlueSQL expression nodes. +impl From< Feed > for Vec< ExprNode< 'static > > +{ + fn from( value : Feed ) -> Self + { + vec! + [ + text( value.link.to_string() ), + value.title.map( text ).unwrap_or( null() ), + value.updated.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ), + value.authors.map( text ).unwrap_or( null() ), + value.description.map( text ).unwrap_or( null() ), + value.published.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ), + text( value.update_period.as_secs().to_string() ), + text( value.config_file ), + ] + } +} diff --git a/module/move/unitore/src/entity/frame.rs b/module/move/unitore/src/entity/frame.rs new file mode 100644 index 0000000000..8fb522ad58 --- /dev/null +++ b/module/move/unitore/src/entity/frame.rs @@ -0,0 +1,280 @@ +//! Frame storing and retrieving functionality. + +use crate::*; +use error_tools::Result; +use gluesql::core:: +{ + ast_builder::{ null, text, timestamp, ExprNode }, chrono::{ DateTime, SecondsFormat, Utc }, executor::Payload +}; +use action::frame::ListReport; + +/// Frame entity. +#[ derive( Debug ) ] +pub struct Frame +{ + /// Frame id. + pub id : String, + /// Frame title. + pub title : Option< String >, + /// Time at which this item was fetched from source. + pub stored_time : Option< DateTime< Utc > >, + /// List of authors of the frame. + pub authors : Option< Vec< String > >, + /// The content of the frame in html or plain text. + pub content : Option< String >, + /// List of links associated with this item of related Web page and attachments. + pub links : Option< Vec< String > >, + /// Short summary, abstract, or excerpt of the frame item. + pub summary : Option< String >, + /// A list of categories that the item belongs to. + pub categories : Option< Vec< String > >, + /// Time at which this item was first published or updated. + pub published : Option< DateTime< Utc > >, + /// Specifies the source feed if the frame was copied from one feed into another feed. + pub source : Option< String >, + /// Information about copyrights over the feed. + pub rights : Option< String >, + /// List of media oblects, encountered in the frame. + pub media : Option< Vec< String > >, + /// The language of the frame. + pub language : Option< String >, + /// Link to feed that contains this frame. + pub feed_link : String, +} + +// qqq : not obvious +// aaa : added explanation +/// Convert from feed_rs feed entry and feed link to Frame struct for convenient use and storage. +impl From< ( feed_rs::model::Entry, String ) > for Frame +{ + fn from( ( entry, feed_link ) : ( feed_rs::model::Entry, String ) ) -> Self + { + let authors = entry.authors + .iter() + .map( | p | p.name.clone() ) + .collect::< Vec< _ > >() + ; + + let content = entry.content + .map( | c | c.body.unwrap_or( c.src.map( | link | link.href ).unwrap_or_default() ) ) + .filter( | s | !s.is_empty() ) + .clone() + ; + + let links = entry.links + .iter() + .map( | link | link.href.clone() ) + .collect::< Vec< _ > >() + .clone() + ; + + let categories = entry.categories + .iter() + .map( | cat | cat.term.clone() ) + .collect::< Vec< _ > >() + ; + + let media = entry.media + .iter() + .flat_map( | m | m.content.clone() ) + .filter_map( | m | m.url.map( | url | url.to_string() ) ) + .collect::< Vec< _ > >() + ; + + Frame + { + id : entry.id, + title : entry.title.map( | title | title.content ).clone(), + stored_time : entry.updated, + authors: ( !authors.is_empty() ).then( || authors ), + // qqq : why join? + // aaa : fixed, saved as list + content, + links: ( !links.is_empty() ).then( || links ), + // qqq : why join? + // aaa : fixed, saved as list + summary : entry.summary.map( | c | c.content ).clone(), + categories: ( !categories.is_empty() ).then( || categories ), + // qqq : why join? + // aaa : fixed, saved as list + published : entry.published.clone(), + source : entry.source.clone(), + rights : entry.rights.map( | r | r.content ).clone(), + media: ( !media.is_empty() ).then( || media ), + // qqq : why join? + // aaa : fixed, saved as list + language : entry.language.clone(), + feed_link, + } + } +} + +/// Frames storing and retrieving. +#[ async_trait::async_trait( ?Send ) ] +pub trait FrameStore +{ + /// Save new frames to storage. + /// New frames will be inserted into `frame` table. + async fn frames_save( &mut self, feed : Vec< Frame > ) -> Result< Payload >; + + /// Update existing frames in storage with new changes. + /// If frames in storage were modified in feed source, they will be changed to match new version. + async fn frames_update( &mut self, feed : Vec< Frame > ) -> Result< () >; + + /// Get all feed frames from storage. + async fn frames_list( &mut self ) -> Result< ListReport >; +} +// qqq : what is update? what update? don't use word update without noun and explanation what deos it mean +// aaa : fixed comments + + +// qqq : what is it for and why? +// aaa : added explanation + +/// Get convenient frame format for using with GlueSQL expression builder. +/// Converts from Frame struct into vec of GlueSQL expression nodes. +impl From< Frame > for Vec< ExprNode< 'static > > +{ + fn from( entry : Frame ) -> Self + { + let title = entry.title + .map( | title | text( title ) ) + .unwrap_or( null() ) + ; + + let stored_time = entry.stored_time + .map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ) + .unwrap_or( null() ) + ; + + let authors = entry.authors + .map( | authors | + text + ( + format!( "[{}]", authors.into_iter().map( | a | format!( "\"{}\"", a ) ).collect::< Vec< _ > >().join( ", " ) ) + ) + ) + .unwrap_or( null() ) + ; + + let content = entry.content + .map( | content | text( content ) ) + .unwrap_or( null() ) + ; + + let links = entry.links + .map( | links | + text + ( + format!( "[{}]", links.into_iter().map( | link | format!( "\"{}\"", link ) ).collect::< Vec< _ > >().join( ", " ) ) + ) + ) + .unwrap_or( null() ) + ; + + let summary = entry.summary + .map( | summary | text( summary ) ) + .unwrap_or( null() ) + ; + + let categories = entry.categories + .map( | categories | + text + ( + format! + ( + "[{}]", + categories.into_iter().map( | category | format!( "\"{}\"", category ) ).collect::< Vec< _ > >().join( ", " ), + ) + ) + ) + .unwrap_or( null() ) + ; + + let published = entry.published + .map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ) + .unwrap_or( null() ) + ; + + let source = entry.source.map( | s | text( s ) ).unwrap_or( null() ); + let rights = entry.rights.map( | r | text( r ) ).unwrap_or( null() ); + let media = entry.media + .map( | media | + text + ( + format!( "[{}]", media.into_iter().map( | media | format!( "\"{}\"", media ) ).collect::< Vec< _ > >().join( ", " ) ) + ) + ) + .unwrap_or( null() ) + ; + + let language = entry.language.clone().map( text ).unwrap_or( null() ); + + vec! + [ + text( entry.id ), + title, + stored_time, + authors, + content, + links, + summary, + categories, + published, + source, + rights, + media, + language, + text( entry.feed_link ) + ] + } +} + +// qqq : RowValue or CellValue? +// aaa : fixed name +/// GlueSQL Value wrapper for display. +#[ derive( Debug ) ] +pub struct CellValue< 'a >( pub &'a gluesql::prelude::Value ); + +impl std::fmt::Display for CellValue< '_ > +{ + fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result + { + use gluesql::prelude::Value::*; + match &self.0 + { + Bool( val ) => write!( f, "{}", val )?, + I8( val ) => write!( f, "{}", val )?, + I16( val ) => write!( f, "{}", val )?, + I32( val ) => write!( f, "{}", val )?, + I64( val ) => write!( f, "{}", val )?, + I128( val ) => write!( f, "{}", val )?, + U8( val ) => write!( f, "{}", val )?, + U16( val ) => write!( f, "{}", val )?, + U32( val ) => write!( f, "{}", val )?, + U64( val ) => write!( f, "{}", val )?, + U128( val ) => write!( f, "{}", val )?, + F32( val ) => write!( f, "{}", val )?, + F64( val ) => write!( f, "{}", val )?, + Str( val ) => write!( f, "{}", val )?, + Null => write!( f, "Null" )?, + Timestamp( val ) => write!( f, "{}", val )?, + _ => write!( f, "" )?, + } + + Ok( () ) + } +} + +impl From< CellValue< '_ > > for String +{ + fn from( value : CellValue< '_ > ) -> Self + { + use gluesql::core::data::Value::*; + match &value.0 + { + Str( val ) => val.clone(), + _ => String::new(), + } + } +} diff --git a/module/move/unitore/src/entity/mod.rs b/module/move/unitore/src/entity/mod.rs new file mode 100644 index 0000000000..94a95a552b --- /dev/null +++ b/module/move/unitore/src/entity/mod.rs @@ -0,0 +1,7 @@ +//! Entities of application. + +pub mod config; +pub mod frame; +pub mod table; +pub mod feed; + diff --git a/module/move/unitore/src/entity/table.rs b/module/move/unitore/src/entity/table.rs new file mode 100644 index 0000000000..b177c3c934 --- /dev/null +++ b/module/move/unitore/src/entity/table.rs @@ -0,0 +1,18 @@ +//! Functionality for storage tables information. + +use crate::*; +use error_tools::Result; +use gluesql::prelude::Payload; + +use action::table::TablesReport; + +/// Functions for tables informantion. +#[ async_trait::async_trait( ?Send ) ] +pub trait TableStore +{ + /// List tables in storage. + async fn tables_list( &mut self ) -> Result< TablesReport >; + + /// List columns of table. + async fn table_list( &mut self, table_name : String ) -> Result< Vec< Payload > >; +} diff --git a/module/move/unitore/src/executor.rs b/module/move/unitore/src/executor.rs new file mode 100644 index 0000000000..8010dbd9cc --- /dev/null +++ b/module/move/unitore/src/executor.rs @@ -0,0 +1,64 @@ +//! Execute plan. + +use crate::*; +use wca::{ Dictionary, Executor, Parser, Verifier }; +use error_tools::Result; + +/// Run feed updates. +pub fn execute() -> Result< (), Box< dyn std::error::Error + Send + Sync > > +{ + // init parser + let parser = Parser; + + // init converter + let dictionary = &Dictionary::former() + .command + ( + command::config::ConfigCommand::add()? + ) + .command + ( + command::config::ConfigCommand::delete()? + ) + .command + ( + command::config::ConfigCommand::list()? + ) + .command + ( + command::frame::FrameCommand::list()? + ) + .command + ( + command::frame::FrameCommand::download()? + ) + .command + ( + command::feed::FeedCommand::list()? + ) + .command + ( + command::table::TablesCommand::list()? + ) + .command + ( + command::table::TableCommand::list()? + ) + .command + ( + command::query::QueryCommand::execute()? + ) + .form(); + let verifier = Verifier; + + // init executor + let executor = Executor::former().form(); + + let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); + let raw_program = parser.parse( args ).unwrap(); + let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); + + executor.program( dictionary, grammar_program )?; + + Ok( () ) +} diff --git a/module/move/unitore/src/executor/actions/table.rs b/module/move/unitore/src/executor/actions/table.rs deleted file mode 100644 index 0c0b6e726d..0000000000 --- a/module/move/unitore/src/executor/actions/table.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Tables metadata commands actions and reports. - -use crate::*; -use gluesql::prelude::Payload; -use std::collections::HashMap; -use executor::{ FeedManager, Report }; -use storage::{ FeedStorage, table::TableStore }; -use error_tools::{ err, BasicError, Result }; - -/// Get labels of column for specified table. -pub async fn list_columns -( - storage : FeedStorage< gluesql::sled_storage::SledStorage >, - args : &wca::Args, -) -> Result< impl Report > -{ - let table_name : String = args - .get_owned::< String >( 0 ) - .ok_or_else::< BasicError, _ >( || err!( "Cannot get 'Name' argument for command .table.list" ) )? - .into() - ; - - let mut manager = FeedManager::new( storage ); - let result = manager.storage.list_columns( table_name.clone() ).await?; - - let mut table_description = String::new(); - let mut columns = std::collections::HashMap::new(); - match &result[ 0 ] - { - Payload::Select { labels: _label_vec, rows: rows_vec } => - { - for row in rows_vec - { - let table = String::from( row[ 0 ].clone() ); - columns.entry( table ) - .and_modify( | vec : &mut Vec< String > | vec.push( String::from( row[ 1 ].clone() ) ) ) - .or_insert( vec![ String::from( row[ 1 ].clone() ) ] ) - ; - } - }, - _ => {}, - } - let mut columns_desc = HashMap::new(); - match table_name.as_str() - { - "feed" => - { - table_description = String::from( "Contains feed items." ); - - for label in columns.get( "feed" ).unwrap() - { - match label.as_str() - { - "link" => { columns_desc.insert - ( - label.clone(), - String::from( "Link to feed source, unique identifier for the feed" ), - ); } - "title" => { columns_desc.insert( label.clone(), String::from( "The title of the feed" ) ); } - "updated" => - { - columns_desc.insert( label.clone(), String::from - ( - "The time at which the feed was last modified. If not provided in the source, or invalid, is Null." - ) ); - }, - "type" => { columns_desc.insert( label.clone(), String::from( "Type of this feed (e.g. RSS2, Atom etc)" ) ); } - "authors" => { columns_desc.insert - ( - label.clone(), - String::from( "Collection of authors defined at the feed level" ) - ); } - "description" => { columns_desc.insert( label.clone(), String::from( "Description of the feed" ) ); } - "published" => { columns_desc.insert - ( - label.clone(), - String::from( "The publication date for the content in the channel" ), - ); } - "update_period" => { columns_desc.insert( label.clone(), String::from( "How often this feed must be updated" ) ); } - _ => { columns_desc.insert( label.clone(), String::from( "Desciption for this column hasn't been added yet!" ) ); } - } - } - }, - "frame" => - { - table_description = String::from( "Contains frame items." ); - for label in columns.get( "frame" ).unwrap() - { - match label.as_str() - { - "id" => { columns_desc.insert( label.clone(), String::from( "A unique identifier for this frame in the feed. " ) ); }, - "title" => { columns_desc.insert( label.clone(), String::from( "Title of the frame" ) ); }, - "updated" => { columns_desc.insert( label.clone(), String::from( "Time at which this item was fetched from source." ) ); }, - "authors" => { columns_desc.insert( label.clone(), String::from( "List of authors of the frame, optional." ) ); }, - "content" => { columns_desc.insert( label.clone(), String::from( "The content of the frame in html or plain text, optional." ) ); }, - "links" => { columns_desc.insert( label.clone(), String::from( "List of links associated with this item of related Web page and attachments." ) ); }, - "summary" => { columns_desc.insert( label.clone(), String::from( "Short summary, abstract, or excerpt of the frame item, optional." ) ); }, - "categories" => { columns_desc.insert( label.clone(), String::from( "Specifies a list of categories that the item belongs to." ) ); }, - "published" => { columns_desc.insert( label.clone(), String::from( "Time at which this item was first published or updated." ) ); }, - "source" => { columns_desc.insert( label.clone(), String::from( "Specifies the source feed if the frame was copied from one feed into another feed, optional." ) ); }, - "rights" => { columns_desc.insert( label.clone(), String::from( "Conveys information about copyrights over the feed, optional." ) ); }, - "media" => { columns_desc.insert( label.clone(), String::from( "List of media oblects, encountered in the frame, optional." ) ); }, - "language" => { columns_desc.insert( label.clone(), String::from( "The language specified on the item, optional." ) ); }, - "feed_link" => { columns_desc.insert( label.clone(), String::from( "Link of feed that contains this frame." ) ); }, - _ => { columns_desc.insert( label.clone(), String::from( "Desciption for this column hasn't been added yet!" ) ); } - } - } - } - "config" => - { - table_description = String::from( "Contains paths to feed config files." ); - for label in columns.get( "config" ).unwrap() - { - match label.as_str() - { - "path" => { columns_desc.insert( label.clone(), String::from( "Path to configuration file" ) ); } - _ => { columns_desc.insert( label.clone(), String::from( "Desciption for this column hasn't been added yet!" ) ); } - } - } - }, - _ => {}, - } - - Ok( ColumnsReport::new( table_name, table_description, columns_desc ) ) -} - -/// Get names of tables in storage. -pub async fn list_tables -( - storage : FeedStorage< gluesql::sled_storage::SledStorage >, - _args : &wca::Args, -) -> Result< impl Report > -{ - let mut manager = FeedManager::new( storage ); - manager.storage.list_tables().await -} - -const EMPTY_CELL : &'static str = ""; - -/// Information about execution of tables commands. -#[ derive( Debug ) ] -pub struct ColumnsReport -{ - table_name : String, - table_description : String, - columns : std::collections::HashMap< String, String > -} - -impl ColumnsReport -{ - /// Create new table columns report. - pub fn new( table_name : String, table_description : String, columns : HashMap< String, String > ) -> Self - { - Self - { - table_name, - table_description, - columns, - } - } -} - -impl std::fmt::Display for ColumnsReport -{ - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result - { - writeln!( f, "Table name: {}", self.table_name )?; - writeln!( f, "Description: {}", self.table_description )?; - - if !self.columns.is_empty() - { - writeln!( f, "Columns:" )?; - let mut rows = Vec::new(); - for ( label, desc ) in &self.columns - { - rows.push - ( - vec! - [ - EMPTY_CELL.to_owned(), - label.clone(), - desc.clone(), - ] - ); - } - let table = table_display::table_with_headers - ( - vec! - [ - EMPTY_CELL.to_owned(), - "label".to_owned(), - "description".to_owned(), - ], - rows, - ); - - if let Some( table ) = table - { - writeln!( f, "{}", table )?; - } - } - else - { - writeln!( f, "No columns" )?; - } - - Ok( () ) - } -} - -impl Report for ColumnsReport {} - -/// Information about execution of tables commands. -#[ derive( Debug ) ] -pub struct TablesReport -{ - tables : std::collections::HashMap< String, ( String, Vec< String > ) > -} - -impl TablesReport -{ - /// Create new report from payload. - pub fn new( payload : Vec< Payload > ) -> Self - { - let mut result = std::collections::HashMap::new(); - match &payload[ 0 ] - { - Payload::Select { labels: _label_vec, rows: rows_vec } => - { - for row in rows_vec - { - let table = String::from( row[ 0 ].clone() ); - let table_description = match table.as_str() - { - "feed" => String::from( "Contains feed items." ), - "frame" => String::from( "Contains frame items." ), - "config" => String::from( "Contains paths to feed config files." ), - _ => String::new(), - }; - result.entry( table ) - .and_modify( | ( _, vec ) : &mut ( String, Vec< String > ) | vec.push( String::from( row[ 1 ].clone() ) ) ) - .or_insert( ( table_description, vec![ String::from( row[ 1 ].clone() ) ] ) ) - ; - } - }, - _ => {}, - } - TablesReport{ tables : result } - } -} - -impl std::fmt::Display for TablesReport -{ - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result - { - writeln!( f, "Storage tables:" )?; - let mut rows = Vec::new(); - for ( table_name, ( desc, columns ) ) in &self.tables - { - let columns_str = if !columns.is_empty() - { - format!( "{};", columns.join( ", " ) ) - } - else - { - String::from( "No columns" ) - }; - - rows.push - ( - vec! - [ - EMPTY_CELL.to_owned(), - table_name.to_owned(), - textwrap::fill( desc, 80 ), - textwrap::fill( &columns_str, 80 ), - ] - ); - } - - let table = table_display::table_with_headers - ( - vec! - [ - EMPTY_CELL.to_owned(), - "name".to_owned(), - "description".to_owned(), - "columns".to_owned(), - ], - rows, - ); - if let Some( table ) = table - { - writeln!( f, "{}", table )?; - } - - Ok( () ) - } -} - -impl Report for TablesReport {} diff --git a/module/move/unitore/src/executor/mod.rs b/module/move/unitore/src/executor/mod.rs deleted file mode 100644 index 6e72cc69bb..0000000000 --- a/module/move/unitore/src/executor/mod.rs +++ /dev/null @@ -1,272 +0,0 @@ -//! Execute plan. - -use super::*; -use feed_config::SubscriptionConfig; -use gluesql::sled_storage::{ sled::Config, SledStorage }; -use retriever::{ FeedClient, FeedFetch }; -use storage::{ Store, FeedStorage, feed::FeedStore, config::ConfigStore, table::TableStore, frame::FrameStore }; -use wca::{ Args, Type }; -use executor::actions::Report; -use error_tools::Result; - -pub mod actions; -use actions:: -{ - frame::{ list_frames, download_frames }, - feed::list_feeds, - config::{ add_config, delete_config, list_configs }, - query::execute_query, - table::{ list_columns, list_tables }, -}; - -fn action< 'a, F, Fut, R >( async_endpoint : F, args : &'a Args ) -> Result< R > -where - F : FnOnce( FeedStorage< SledStorage >, &'a Args ) -> Fut, - Fut : std::future::Future< Output = Result< R > >, - R : actions::Report, -{ - let path_to_storage = std::env::var( "UNITORE_STORAGE_PATH" ) - .unwrap_or( String::from( "./_data" ) ) - ; - - let config = Config::default() - .path( path_to_storage ) - ; - let rt = tokio::runtime::Runtime::new()?; - - rt.block_on( async move - { - let feed_storage = FeedStorage::init_storage( config ).await?; - async_endpoint( feed_storage, args ).await - } ) -} - -/// Run feed updates. -pub fn execute() -> Result< (), Box< dyn std::error::Error + Send + Sync > > -{ - let ca = wca::CommandsAggregator::former() - .command( "frames.download" ) - .hint( "Download frames from feed sources provided in config files." ) - .long_hint(concat! - ( - "Download frames from feed sources provided in config files.\n", - " Example: .frames.download", - )) - .routine( | args | - { - match action( download_frames, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "feeds.list" ) - .long_hint( concat! - ( - "List all feeds from storage.\n", - " Example: .feeds.list", - )) - .routine( | args | - { - match action( list_feeds, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "frames.list" ) - .long_hint( concat! - ( - "List all frames saved in storage.\n", - " Example: .frames.list", - )) - .routine( | args | - { - match action( list_frames, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "config.add" ) - .long_hint( concat! - ( - "Add file with feeds configurations. Subject: path to config file.\n", - " Example: .config.add ./config/feeds.toml\n", - " The file should contain config entities with fields:\n", - " - `update_period` : update frequency for feed. Example values: `12h`, `1h 20min`, `2days 5h`;\n", - " - `link` : URL for feed source;\n\n", - " Example:\n", - " [[config]]\n", - " update_period = \"1min\"\n", - " link = \"https://feeds.bbci.co.uk/news/world/rss.xml\"\n", - )) - .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() - .routine( | args : Args | - { - match action( add_config, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "config.delete" ) - .long_hint( concat! - ( - "Delete file with feeds configuraiton. Subject: path to config file.\n", - " Example: .config.delete ./config/feeds.toml", - )) - .subject().hint( "Path" ).kind( Type::Path ).optional( false ).end() - .routine( | args : Args | - { - match action( delete_config, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "config.list" ) - .long_hint( concat! - ( - "List all config files saved in storage.\n", - " Example: .config.list", - )) - .routine( | args | - { - match action( list_configs, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "tables.list" ) - .long_hint( concat! - ( - "List all tables saved in storage.\n", - " Example: .tables.list", - )) - .routine( | args | - { - match action( list_tables, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "table.list" ) - .long_hint( concat! - ( - "List fields of specified table.\n", - "Subject: table name.\n", - " Example: .table.list feed", - )) - .subject().hint( "Name" ).kind( wca::Type::String ).optional( false ).end() - .routine( | args : Args | - { - match action( list_columns, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - - .command( "query.execute" ) - .long_hint( concat! - ( - "Execute custom query. Subject: query string, with special characters escaped.\n", - " Example query:\n", - " - select all frames:\n", - r#" .query.execute \'SELECT \* FROM frame\'"#, - "\n", - " - select title and link to the most recent frame:\n", - r#" .query.execute \'SELECT title, links, MIN\(published\) FROM frame\'"#, - "\n\n", - )) - .subject().hint( "Query" ).kind( Type::List( Type::String.into(), ' ' ) ).optional( false ).end() - .routine( | args : Args | - { - match action( execute_query, &args ) - { - Ok( report ) => report.report(), - Err( err ) => println!( "{:?}", err ), - } - }) - .end() - .help_variants( [ wca::HelpVariants::General, wca::HelpVariants::SubjectCommand ] ) - .perform(); - - let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); - ca.perform( args )?; - - Ok( () ) -} - -/// Manages feed subsriptions and updates. -pub struct FeedManager< C, S : FeedStore + ConfigStore + FrameStore + Store + Send > -{ - /// Subscription configuration with link and update period. - pub config : Vec< SubscriptionConfig >, - /// Storage for saving feed. - pub storage : S, - /// Client for fetching feed from links in FeedConfig. - pub client : C, -} - -impl< C, S : FeedStore + ConfigStore + FrameStore + Store + Send > std::fmt::Debug for FeedManager< C, S > -{ - fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result - { - writeln!(f, "Feed manager with storage and client" ) - } -} - -impl< S : FeedStore + ConfigStore + FrameStore + TableStore + Store + Send > FeedManager< FeedClient, S > -{ - /// Create new instance of FeedManager. - pub fn new( storage : S ) -> FeedManager< FeedClient, S > - { - Self - { - storage, - config : Vec::new(), - client : FeedClient, - } - } -} - -impl< C : FeedFetch, S : FeedStore + ConfigStore + FrameStore + TableStore + Store + Send > FeedManager< C, S > -{ - /// Set configurations for subscriptions. - pub fn set_config( &mut self, configs : Vec< SubscriptionConfig > ) - { - self.config = configs; - } - - /// Set client for fetching feed. - pub fn set_client( &mut self, client : C ) - { - self.client = client; - } - - /// Execute custom query, print result. - pub async fn execute_custom_query( &mut self, query : String ) -> Result< impl actions::Report > - { - self.storage.execute_query( query ).await - } - -} diff --git a/module/move/unitore/src/feed_config.rs b/module/move/unitore/src/feed_config.rs index 71d852731f..88b5f8a791 100644 --- a/module/move/unitore/src/feed_config.rs +++ b/module/move/unitore/src/feed_config.rs @@ -23,7 +23,15 @@ pub struct Subscriptions pub config : Vec< SubscriptionConfig > } -/// Reads provided configuration file with list of subscriptions. +/// Get list of feed subscriptions from provided configuration file. +/// +/// # Arguments +/// +/// * `file_path` - Path to the configuration file. +/// +/// # Returns +/// +/// Result with list of feed subscriptions serialized as SubscriptionConfig. pub fn read( file_path : String ) -> Result< Vec< SubscriptionConfig > > { let read_file = OpenOptions::new() diff --git a/module/move/unitore/src/lib.rs b/module/move/unitore/src/lib.rs index d871675d0c..f6e0df9632 100644 --- a/module/move/unitore/src/lib.rs +++ b/module/move/unitore/src/lib.rs @@ -2,7 +2,11 @@ pub mod retriever; pub mod feed_config; pub mod executor; -pub mod storage; -pub mod table_display; +pub mod tool; +pub mod command; +pub mod action; +pub mod entity; +pub mod sled_adapter; // qqq : src/Readmу.md with file structure please +// aaa : added Readme.md diff --git a/module/move/unitore/src/main.rs b/module/move/unitore/src/main.rs index b85f4af722..49e83d2564 100644 --- a/module/move/unitore/src/main.rs +++ b/module/move/unitore/src/main.rs @@ -1,4 +1,6 @@ -//! qqq : ? +//! Runs unitore command executor. +//! qqq : ? aaa: added documantation. + pub use unitore::executor; fn main() -> Result< (), Box< dyn std::error::Error + Send + Sync > > diff --git a/module/move/unitore/src/retriever.rs b/module/move/unitore/src/retriever.rs index 32a3f4e47a..ac4e94be11 100644 --- a/module/move/unitore/src/retriever.rs +++ b/module/move/unitore/src/retriever.rs @@ -12,22 +12,24 @@ use feed_rs::parser as feed_parser; use error_tools::{ Result, for_app::Context }; // qqq : purpose of trait if any? -/// Fetch feed from provided source link. -#[ async_trait::async_trait ] -pub trait FeedFetch -{ - /// Get feed from source specified by its link. - async fn fetch( &self, source : url::Url ) -> Result< feed_rs::model::Feed >; -} +// aaa : removed unnecessary trait /// Feed client for fetching feed. #[ derive( Debug ) ] pub struct FeedClient; -#[ async_trait::async_trait ] -impl FeedFetch for FeedClient +impl FeedClient { - async fn fetch( &self, source : url::Url ) -> Result< feed_rs::model::Feed > + /// Fetch feed frames from provided url source. + /// + /// # Arguments + /// + /// * `source` - The link to feed source. + /// + /// # Returns + /// + /// Result with fetched feed as feed_rs Feed struct. + pub async fn fetch( &self, source : url::Url ) -> Result< feed_rs::model::Feed > { let https = HttpsConnector::new(); let client = Client::builder( TokioExecutor::new() ).build::< _, Empty< Bytes > >( https ); diff --git a/module/move/unitore/src/sled_adapter/config.rs b/module/move/unitore/src/sled_adapter/config.rs new file mode 100644 index 0000000000..a3b0cc73d8 --- /dev/null +++ b/module/move/unitore/src/sled_adapter/config.rs @@ -0,0 +1,56 @@ +//! Config file operation with Sled storage. + +use crate::*; +use error_tools::{ err, Result }; +use gluesql:: +{ + core:: + { + ast_builder::{ col, table, text, Execute, }, + executor::Payload, + }, + sled_storage::SledStorage, +}; +use entity::config::{ Config, ConfigStore }; +use sled_adapter::FeedStorage; + +#[ async_trait::async_trait( ?Send ) ] +impl ConfigStore for FeedStorage< SledStorage > +{ + async fn config_add( &mut self, config : &Config ) -> Result< Payload > + { + let res = table( "config" ) + .insert() + .columns + ( + "path", + ) + .values( vec![ vec![ text( config.path() ) ] ] ) + .execute( &mut *self.0.lock().await ) + .await; + + Ok( res? ) + } + + async fn config_delete( &mut self, config : &Config ) -> Result< Payload > + { + let res = table( "config" ) + .delete() + .filter( col( "path" ).eq( format!( "'{}'", config.path() ) ) ) + .execute( &mut *self.0.lock().await ) + .await?; + + if res == Payload::Delete( 0 ) + { + return Err( err!( format!( "Config file with path {} not found in storage", config.path() ) ) ) + } + + Ok( res ) + } + + async fn config_list( &mut self ) -> Result< Payload > + { + let res = table( "config" ).select().execute( &mut *self.0.lock().await ).await?; + Ok( res ) + } +} diff --git a/module/move/unitore/src/sled_adapter/feed.rs b/module/move/unitore/src/sled_adapter/feed.rs new file mode 100644 index 0000000000..fb38e02075 --- /dev/null +++ b/module/move/unitore/src/sled_adapter/feed.rs @@ -0,0 +1,197 @@ +//! Feed operation with Sled storage. + +use crate::*; +use std::time::Duration; +use error_tools::{ Result, for_app::Context }; +use gluesql:: +{ + core:: + { + ast_builder::{ col, null, table, text, Execute, timestamp, ExprNode }, + executor::Payload, + data::Value, + chrono::SecondsFormat, + }, + sled_storage::SledStorage, +}; +use entity:: +{ + feed::{ Feed, FeedStore }, + frame::FrameStore, +}; +use action:: +{ + feed::FeedsReport, + frame::{ UpdateReport, SelectedEntries, FramesReport }, +}; +use sled_adapter::FeedStorage; +use wca::wtools::Itertools; + +#[ async_trait::async_trait( ?Send ) ] +impl FeedStore for FeedStorage< SledStorage > +{ + async fn feeds_list( &mut self ) -> Result< FeedsReport > + { + let res = table( "feed" ) + .select() + .project( "title, link, update_period, config_file" ) + .execute( &mut *self.0.lock().await ) + .await? + ; + + let mut report = FeedsReport::new(); + match res + { + Payload::Select { labels: label_vec, rows: rows_vec } => + { + report.0 = SelectedEntries + { + selected_rows : rows_vec, + selected_columns : label_vec, + } + }, + _ => {}, + } + + Ok( report ) + } + + async fn feeds_update( &mut self, feed : Vec< Feed > ) -> Result< () > + { + for feed in feed + { + let _update = table( "feed" ) + .update() + .set( "title", feed.title.map( text ).unwrap_or( null() ) ) + .set( + "updated", + feed.updated.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ), + ) + .set( "authors", feed.authors.map( text ).unwrap_or( null() ) ) + .set( "description", feed.description.map( text ).unwrap_or( null() ) ) + .set( + "published", + feed.published.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ), + ) + .filter( col( "link" ).eq( feed.link.to_string() ) ) + .execute( &mut *self.0.lock().await ) + .await + .context( "Failed to insert feed" )? + ; + } + + Ok( () ) + } + + async fn feeds_process + ( + &mut self, + feeds : Vec< ( feed_rs::model::Feed, Duration, url::Url ) >, + ) -> Result< UpdateReport > + { + let mut new_entries = Vec::new(); + let mut modified_entries = Vec::new(); + let mut reports = Vec::new(); + + for feed in &feeds + { + let mut frames_report = FramesReport::new( feed.0.title.clone().unwrap().content ); + + let existing_frames = table( "frame" ) + .select() + .filter( col( "feed_link" ).eq( text( feed.2.to_string() ) ) ) + .project( "id, published" ) + .execute( &mut *self.0.lock().await ) + .await + .context( "Failed to get existing frames while saving new frames" )? + ; + + if let Some( rows ) = existing_frames.select() + { + let rows = rows.collect::< Vec< _ > >(); + frames_report.existing_frames = rows.len(); + let existing_entries = rows.iter() + .map( | r | ( r.get( "id" ).map( | &val | val.clone() ), r.get( "published" ).map( | &val | val.clone() ) ) ) + .flat_map( | ( id, published ) | + id.map( | id | + ( + id, + published.map( | date | + { + match date + { + Value::Timestamp( date_time ) => Some( date_time ), + _ => None, + } + } ) + .flatten() + ) + ) + ) + .flat_map( | ( id, published ) | match id { Value::Str( id ) => Some( ( id, published ) ), _ => None } ) + .collect_vec() + ; + + let existing_ids = existing_entries.iter().map( | ( id, _ ) | id ).collect_vec(); + for entry in &feed.0.entries + { + // if extry with same id is already in db, check if it is updated + if let Some( position ) = existing_ids.iter().position( | &id | id == &entry.id ) + { + if let Some( date ) = existing_entries[ position ].1 + { + if date.and_utc() != entry.published.unwrap() + { + frames_report.updated_frames += 1; + modified_entries.push( ( entry.clone(), feed.2.to_string() ).into() ); + } + } + } + else + { + frames_report.new_frames += 1; + new_entries.push( ( entry.clone(), feed.2.to_string() ).into() ); + } + } + } + reports.push( frames_report ); + } + + if !new_entries.is_empty() + { + let _saved_report = self.frames_save( new_entries ).await?; + } + if !modified_entries.is_empty() + { + let _updated_report = self.frames_update( modified_entries ).await?; + } + + Ok( UpdateReport( reports ) ) + } + + async fn feeds_save( &mut self, feed : Vec< Feed > ) -> Result< Payload > + { + let feeds_rows : Vec< Vec< ExprNode< 'static > > > = feed.into_iter().map( | feed | feed.into() ).collect_vec(); + + let insert = table( "feed" ) + .insert() + .columns + ( + "link, + title, + updated, + authors, + description, + published, + update_period, + config_file", + ) + .values( feeds_rows ) + .execute( &mut *self.0.lock().await ) + .await + .context( "Failed to insert feeds" )? + ; + + Ok( insert ) + } +} diff --git a/module/move/unitore/src/sled_adapter/frame.rs b/module/move/unitore/src/sled_adapter/frame.rs new file mode 100644 index 0000000000..84d4687bf4 --- /dev/null +++ b/module/move/unitore/src/sled_adapter/frame.rs @@ -0,0 +1,124 @@ +//! Frames operation with Sled storage. + +use crate::*; +use std::collections::HashMap; +use error_tools::{ Result, for_app::Context }; +use gluesql:: +{ + core:: + { + ast_builder::{ col, table, Execute, ExprNode }, + executor::Payload, + data::Value, + }, + sled_storage::SledStorage, +}; +use entity::frame::{ FrameStore, Frame }; +use action::frame::{ SelectedEntries, FramesReport, ListReport }; +use sled_adapter::FeedStorage; +use wca::wtools::Itertools; + +#[ async_trait::async_trait( ?Send ) ] +impl FrameStore for FeedStorage< SledStorage > +{ + async fn frames_list( &mut self ) -> Result< ListReport > + { + let res = table( "frame" ).select().execute( &mut *self.0.lock().await ).await?; + + let mut reports = Vec::new(); + let all_frames = + if let Payload::Select { labels: label_vec, rows: rows_vec } = res + { + SelectedEntries + { + selected_rows : rows_vec, + selected_columns : label_vec, + } + } + else + { + SelectedEntries::new() + }; + + let mut feeds_map = HashMap::new(); + + for row in all_frames.selected_rows + { + let title_val = row.last().unwrap().clone(); + let title = String::from( title_val ); + feeds_map.entry( title ) + .and_modify( | vec : &mut Vec< Vec< Value > > | vec.push( row.clone() ) ) + .or_insert( vec![ row ] ) + ; + } + + for ( title, frames ) in feeds_map + { + let mut report = FramesReport::new( title ); + report.existing_frames = frames.len(); + report.selected_frames = SelectedEntries + { + selected_rows : frames, + selected_columns : all_frames.selected_columns.clone(), + }; + reports.push( report ); + } + + Ok( ListReport( reports ) ) + } + + async fn frames_save( &mut self, frames : Vec< Frame > ) -> Result< Payload > + { + let entries_rows : Vec< Vec< ExprNode< 'static > > > = frames.into_iter().map( | entry | entry.into() ).collect_vec(); + + let insert = table( "frame" ) + .insert() + .columns + ( + "id, + title, + stored_time, + authors, + content, + links, + summary, + categories, + published, + source, + rights, + media, + language, + feed_link" + ) + .values( entries_rows ) + .execute( &mut *self.0.lock().await ) + .await + .context( "Failed to insert frames" )? + ; + + Ok( insert ) + } + + async fn frames_update( &mut self, feed : Vec< Frame > ) -> Result< () > + { + let entries_rows : Vec< Vec< ExprNode< 'static > > > = feed.into_iter().map( | entry | entry.into() ).collect_vec(); + + for entry in entries_rows + { + let _update = table( "frame" ) + .update() + .set( "title", entry[ 1 ].to_owned() ) + .set( "content", entry[ 4 ].to_owned() ) + .set( "links", entry[ 5 ].to_owned() ) + .set( "summary", entry[ 6 ].to_owned() ) + .set( "published", entry[ 8 ].to_owned() ) + .set( "media", entry[ 9 ].to_owned() ) + .filter( col( "id" ).eq( entry[ 0 ].to_owned() ) ) + .execute( &mut *self.0.lock().await ) + .await + .context( "Failed to update frames" )? + ; + } + Ok( () ) + } +} diff --git a/module/move/unitore/src/sled_adapter/mod.rs b/module/move/unitore/src/sled_adapter/mod.rs new file mode 100644 index 0000000000..ac4780a6f1 --- /dev/null +++ b/module/move/unitore/src/sled_adapter/mod.rs @@ -0,0 +1,117 @@ +//! Storage for frames, feeds and config files. + +use crate::*; +use std::sync::Arc; +use error_tools::{ for_app::Context, Result }; +use tokio::sync::Mutex; +use gluesql:: +{ + core:: + { + ast_builder::{ table, Build, Execute }, + store::{ GStore, GStoreMut }, + }, + prelude::Glue, + sled_storage::{ sled::Config, SledStorage }, +}; +use action::query::QueryReport; + +mod frame; +mod table; +mod feed; +mod config; + +/// Storage for feed frames. +#[ derive( Clone ) ] +pub struct FeedStorage< S : GStore + GStoreMut + Send >( Arc< Mutex< Glue< S > > > ); + +impl< S : GStore + GStoreMut + Send > std::fmt::Debug for FeedStorage< S > +{ + fn fmt( &self, f : &mut std::fmt::Formatter< '_ > ) -> std::fmt::Result + { + writeln!( f, "GlueSQL storage" ) + } +} + +impl FeedStorage< SledStorage > +{ + /// Initialize new storage from configuration, create feed table. + pub async fn init_storage( config : &Config ) -> Result< Self > + { + let storage = SledStorage::try_from( config.clone() ) + .context( format!( "Failed to initialize storage with config {:?}", config ) )? + ; + + let mut glue = Glue::new( storage ); + + let config_table = table( "config" ) + .create_table_if_not_exists() + .add_column( "path TEXT PRIMARY KEY" ) + .build()? + ; + + config_table.execute( &mut glue ).await?; + + let feed_table = table( "feed" ) + .create_table_if_not_exists() + .add_column( "link TEXT PRIMARY KEY" ) + .add_column( "type TEXT" ) + .add_column( "title TEXT" ) + .add_column( "updated TIMESTAMP" ) + .add_column( "authors TEXT" ) + .add_column( "description TEXT" ) + .add_column( "published TIMESTAMP" ) + .add_column( "update_period TEXT" ) + .add_column( "config_file TEXT FOREIGN KEY REFERENCES config(path)" ) + .build()? + ; + + feed_table.execute( &mut glue ).await?; + + let frame_table = table( "frame" ) + .create_table_if_not_exists() + .add_column( "id TEXT PRIMARY KEY" ) + .add_column( "title TEXT" ) + .add_column( "stored_time TIMESTAMP" ) + .add_column( "authors LIST" ) + .add_column( "content TEXT" ) + .add_column( "links LIST" ) + .add_column( "summary TEXT" ) + .add_column( "categories LIST" ) + .add_column( "published TIMESTAMP" ) + .add_column( "source TEXT" ) + .add_column( "rights TEXT" ) + .add_column( "media LIST" ) + .add_column( "language TEXT" ) + .add_column( "feed_link TEXT FOREIGN KEY REFERENCES feed(link)" ) + .build()? + ; + + frame_table.execute( &mut glue ).await?; + + Ok( Self( Arc::new( Mutex::new( glue ) ) ) ) + } +} + +/// Functionality of feed storage. +#[ mockall::automock ] +#[ async_trait::async_trait( ?Send ) ] +pub trait Store +{ + /// Execute custom query passed as String. + async fn query_execute( &mut self, query : String ) -> Result< QueryReport >; +} + +#[ async_trait::async_trait( ?Send ) ] +impl< S : GStore + GStoreMut + Send > Store for FeedStorage< S > +{ + async fn query_execute( &mut self, query : String ) -> Result< QueryReport > + { + let glue = &mut *self.0.lock().await; + let payloads = glue.execute( &query ).await.context( "Failed to execute query" )?; + + let report = QueryReport ( payloads ); + + Ok( report ) + } +} diff --git a/module/move/unitore/src/sled_adapter/table.rs b/module/move/unitore/src/sled_adapter/table.rs new file mode 100644 index 0000000000..71763918ee --- /dev/null +++ b/module/move/unitore/src/sled_adapter/table.rs @@ -0,0 +1,35 @@ +//! Table and columns info operations from Sled storage. + +use crate::*; +use error_tools::Result; +use gluesql:: +{ + core::executor::Payload, + sled_storage::SledStorage, +}; +use entity::table::TableStore; +use action::table::TablesReport; +use sled_adapter::FeedStorage; + +#[ async_trait::async_trait( ?Send ) ] +impl TableStore for FeedStorage< SledStorage > +{ + async fn tables_list( &mut self ) -> Result< TablesReport > + { + let glue = &mut *self.0.lock().await; + let payloads = glue.execute( "SELECT * FROM GLUE_TABLE_COLUMNS" ).await?; + + let report = TablesReport::new( payloads ); + + Ok( report ) + } + + async fn table_list( &mut self, table_name : String ) -> Result< Vec< Payload > > + { + let glue = &mut *self.0.lock().await; + let query_str = format!( "SELECT * FROM GLUE_TABLE_COLUMNS WHERE TABLE_NAME='{}'", table_name ); + let payloads = glue.execute( &query_str ).await?; + + Ok( payloads ) + } +} \ No newline at end of file diff --git a/module/move/unitore/src/storage/config.rs b/module/move/unitore/src/storage/config.rs deleted file mode 100644 index 5a3770ee8e..0000000000 --- a/module/move/unitore/src/storage/config.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Functionality for storing and retrieving config files. - -use super::*; -use error_tools::{ err, Result }; -use gluesql:: -{ - core:: - { - ast_builder::{ col, table, text, Execute }, - executor::Payload, - }, - sled_storage::SledStorage, -}; - -/// Config file path. -#[ derive( Debug ) ] -pub struct Config( pub String ); - -impl Config -{ - /// Create new config with provided path. - pub fn new( path : String ) -> Self - { - Self( path ) - } - - /// Get path of config file. - pub fn path( &self ) -> String - { - self.0.clone() - } -} - -/// Functionality of config storing. -#[ async_trait::async_trait( ?Send ) ] -pub trait ConfigStore -{ - /// Add subscription. - async fn add_config( &mut self, config : &Config ) -> Result< Payload >; - - /// Remove subscription. - async fn delete_config( &mut self, config : &Config ) -> Result< Payload >; - - /// List subscriptions. - async fn list_configs( &mut self ) -> Result< Payload >; -} - -// qqq : port and adapters should not be in the same file -// Ideally, they should be in different crates, but you should at least put them in different folders -// there should be a `sled_adapter`` folder - -#[ async_trait::async_trait( ?Send ) ] -impl ConfigStore for FeedStorage< SledStorage > -{ - async fn add_config( &mut self, config : &Config ) -> Result< Payload > - { - let res = table( "config" ) - .insert() - .columns - ( - "path", - ) - .values( vec![ vec![ text( config.path() ) ] ] ) - .execute( &mut *self.storage.lock().await ) - .await; - - // let res = match &res - // { - // Err( err ) => - // { - // if let gluesql::core::error::Error::Validate( val_err ) = err - // { - // let res = match val_err - // { - // gluesql::core::error::ValidateError::DuplicateEntryOnPrimaryKeyField( _ ) => - // { - // res.context( "Config with same path already exists." ) - // }, - // _ => res.into() - // }; - - // res - // } - // res.into() - // }, - // Ok( _ ) => res.into(), - // }; - - Ok( res? ) - } - - async fn delete_config( &mut self, config : &Config ) -> Result< Payload > - { - let res = table( "config" ) - .delete() - .filter( col( "path" ).eq( format!( "'{}'", config.path() ) ) ) - .execute( &mut *self.storage.lock().await ) - .await?; - - if res == Payload::Delete( 0 ) - { - return Err( err!( format!( "Config file with path {} not found in storage", config.path() ) ) ) - } - - Ok( res ) - } - - async fn list_configs( &mut self ) -> Result< Payload > - { - let res = table( "config" ).select().execute( &mut *self.storage.lock().await ).await?; - Ok( res ) - } -} - -// qqq : use AbsolutePath newtype from `path_tools` -// qqq : normalize all paths with `path_tools::path::normalize` -// https://docs.rs/proper_path_tools/latest/proper_path_tools/path/fn.normalize.html - -// unitore .query.execute \'SELECT \* FROM feed\' -// qqq : something is broken in this table. also lack of association with config files - -// unitore .query.execute \'SELECT \* FROM x\' -// qqq : it is not obvious where one record ends and another begins diff --git a/module/move/unitore/src/storage/feed.rs b/module/move/unitore/src/storage/feed.rs deleted file mode 100644 index d28a1182ab..0000000000 --- a/module/move/unitore/src/storage/feed.rs +++ /dev/null @@ -1,279 +0,0 @@ -//! Feed storage entity and storage functions. - -use crate::*; -use std::time::Duration; -use error_tools::{ for_app::Context, Result }; -use gluesql:: -{ - core:: - { - ast_builder::{ null, col, table, text, Execute, timestamp, ExprNode }, - data::Value, - executor::Payload, - chrono::{ Utc, DateTime, SecondsFormat }, - }, - sled_storage::SledStorage, -}; - -use executor::actions:: -{ - feed::FeedsReport, - frame::{ UpdateReport, SelectedEntries, FramesReport }, -}; -use storage::{ FeedStorage, frame::FrameStore }; -use wca::wtools::Itertools; - -/// Feed item. -#[ derive( Debug ) ] -pub struct Feed -{ - /// Link to feed source. - pub link : url::Url, - /// Title of feed. - pub title : Option< String >, - /// Last time the feed was fetched. - pub updated : Option< DateTime< Utc > >, - /// Authors of feed. - pub authors : Option< String >, - /// Short description of feed content. - pub description : Option< String >, - /// Date and time when feed was published. - pub published : Option< DateTime< Utc > >, - /// How often the feed frames must be fetched. - pub update_period : Duration, -} - -impl Feed -{ - /// Create new feed item from source url and update period. - pub fn new( link : url::Url, update_period : Duration ) -> Self - { - Self - { - link, - title : None, - updated : None, - authors : None, - description : None, - published : None, - update_period, - } - } -} - -/// Functionality of feed storage. -#[ mockall::automock ] -#[ async_trait::async_trait( ?Send ) ] -pub trait FeedStore -{ - - /// Insert items from list into feed table. - async fn update_feed( &mut self, feed : Vec< Feed > ) -> Result< () >; - - /// Process fetched feed, new items will be saved, modified items will be updated. - async fn process_feeds( &mut self, feeds : Vec< ( feed_rs::model::Feed, Duration, url::Url ) > ) -> Result< UpdateReport >; - - /// Get all feeds from storage. - async fn get_all_feeds( &mut self ) -> Result< FeedsReport >; - - /// Add feeds entries. - async fn save_feeds( &mut self, feeds : Vec< Feed > ) -> Result< Payload >; -} -// qqq : poor description and probably naming. improve, please - -#[ async_trait::async_trait( ?Send ) ] -impl FeedStore for FeedStorage< SledStorage > -{ - async fn get_all_feeds( &mut self ) -> Result< FeedsReport > - { - let res = table( "feed" ).select().project( "title, link, update_period" ).execute( &mut *self.storage.lock().await ).await?; - let mut report = FeedsReport::new(); - match res - { - Payload::Select { labels: label_vec, rows: rows_vec } => - { - report.0 = SelectedEntries - { - selected_rows : rows_vec, - selected_columns : label_vec, - } - }, - _ => {}, - } - - Ok( report ) - } - - async fn update_feed( &mut self, feed : Vec< Feed > ) -> Result< () > - { - for feed in feed - { - let _update = table( "feed" ) - .update() - .set( "title", feed.title.map( text ).unwrap_or( null() ) ) - .set( "updated", feed.updated.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ) ) - .set( "authors", feed.authors.map( text ).unwrap_or( null() ) ) - .set( "description", feed.description.map( text ).unwrap_or( null() ) ) - .set( "published", feed.published.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ) ) - .filter( col( "link" ).eq( feed.link.to_string() ) ) - .execute( &mut *self.storage.lock().await ) - .await - .context( "Failed to insert feed" )? - ; - } - - Ok( () ) - } - - async fn process_feeds - ( - &mut self, - feeds : Vec< ( feed_rs::model::Feed, Duration, url::Url ) >, - ) -> Result< UpdateReport > - { - let mut new_entries = Vec::new(); - let mut modified_entries = Vec::new(); - let mut reports = Vec::new(); - - for feed in &feeds - { - let mut frames_report = FramesReport::new( feed.0.title.clone().unwrap().content ); - - let existing_frames = table( "frame" ) - .select() - .filter( col( "feed_link" ).eq( text( feed.2.to_string() ) ) ) - .project( "id, published" ) - .execute( &mut *self.storage.lock().await ) - .await - .context( "Failed to get existing frames while saving new frames" )? - ; - - if let Some( rows ) = existing_frames.select() - { - let rows = rows.collect::< Vec< _ > >(); - frames_report.existing_frames = rows.len(); - let existing_entries = rows.iter() - .map( | r | ( r.get( "id" ).map( | &val | val.clone() ), r.get( "published" ).map( | &val | val.clone() ) ) ) - .flat_map( | ( id, published ) | - id.map( | id | - ( - id, - published.map( | date | - { - match date - { - Value::Timestamp( date_time ) => Some( date_time ), - _ => None, - } - } ) - .flatten() - ) - ) - ) - .flat_map( | ( id, published ) | match id { Value::Str( id ) => Some( ( id, published ) ), _ => None } ) - .collect_vec() - ; - - let existing_ids = existing_entries.iter().map( | ( id, _ ) | id ).collect_vec(); - for entry in &feed.0.entries - { - // if extry with same id is already in db, check if it is updated - if let Some( position ) = existing_ids.iter().position( | &id | id == &entry.id ) - { - if let Some( date ) = existing_entries[ position ].1 - { - if date.and_utc() != entry.published.unwrap() - { - frames_report.updated_frames += 1; - modified_entries.push( ( entry.clone(), feed.2.to_string() ).into() ); - } - } - } - else - { - frames_report.new_frames += 1; - new_entries.push( ( entry.clone(), feed.2.to_string() ).into() ); - } - } - } - reports.push( frames_report ); - } - - if new_entries.len() > 0 - { - let _saved_report = self.save_frames( new_entries ).await?; - } - if modified_entries.len() > 0 - { - let _updated_report = self.update_frames( modified_entries ).await?; - } - - Ok( UpdateReport( reports ) ) - } - - async fn save_feeds( &mut self, feed : Vec< Feed > ) -> Result< Payload > - { - let feeds_rows : Vec< Vec< ExprNode< 'static > > > = feed.into_iter().map( | feed | feed.into() ).collect_vec(); - - let insert = table( "feed" ) - .insert() - .columns - ( - "link, - title, - updated, - authors, - description, - published, - update_period", - ) - .values( feeds_rows ) - .execute( &mut *self.storage.lock().await ) - .await - .context( "Failed to insert feeds" )? - ; - - Ok( insert ) - } -} - -impl From< ( feed_rs::model::Feed, Duration, url::Url ) > for Feed -{ - fn from( val : ( feed_rs::model::Feed, Duration, url::Url ) ) -> Self - { - let duration = val.1; - let link = val.2; - let value = val.0; - - let authors = value.authors.into_iter().map( | p | p.name ).collect::< Vec< _ > >(); - let description = value.description.map( | desc | desc.content ); - - Self - { - link, - title : value.title.map( | title | title.content ), - updated : value.updated, - published : value.published, - description, - authors : ( !authors.is_empty() ).then( || authors.join( ", " ) ), - update_period : duration, - } - } -} - -impl From< Feed > for Vec< ExprNode< 'static > > -{ - fn from( value : Feed ) -> Self - { - vec! - [ - text( value.link.to_string() ), - value.title.map( text ).unwrap_or( null() ), - value.updated.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ), - value.authors.map( text ).unwrap_or( null() ), - value.description.map( text ).unwrap_or( null() ), - value.published.map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ).unwrap_or( null() ), - text( value.update_period.as_secs().to_string() ), - ] - } -} diff --git a/module/move/unitore/src/storage/frame.rs b/module/move/unitore/src/storage/frame.rs deleted file mode 100644 index 49379e472f..0000000000 --- a/module/move/unitore/src/storage/frame.rs +++ /dev/null @@ -1,331 +0,0 @@ -//! Frame storing and retrieving functionality. - -use crate::*; -use std::collections::HashMap; -use error_tools::{ for_app::Context, Result }; -use gluesql:: -{ - core:: - { - ast_builder::{ null, col, table, text, Execute, timestamp, ExprNode }, - data::Value, - executor::Payload, - chrono::{ Utc, DateTime, SecondsFormat }, - }, - sled_storage::SledStorage, -}; - -use executor::actions::frame::{ FramesReport, ListReport, SelectedEntries }; -use storage::FeedStorage; -use wca::wtools::Itertools; - -/// Frame entity. -#[ derive( Debug ) ] -pub struct Frame -{ - /// Frame id. - pub id : String, - /// Frame title. - pub title : Option< String >, - updated : Option< DateTime< Utc > >, - authors : Option< String >, - content : Option< String >, - links : Option< String >, - summary : Option< String >, - categories : Option< String >, - published : Option< DateTime< Utc > >, - source : Option< String >, - rights : Option< String >, - media : Option< String >, - language : Option< String >, - feed_link : String, -} - -// qqq : not obvious -impl From< ( feed_rs::model::Entry, String ) > for Frame -{ - fn from( ( entry, feed_link ) : ( feed_rs::model::Entry, String ) ) -> Self - { - let authors = entry.authors - .iter() - .map( | p | p.name.clone() ) - .collect::< Vec< _ > >() - ; - - let content = entry.content - .map( | c | c.body.unwrap_or( c.src.map( | link | link.href ).unwrap_or_default() ) ) - .filter( | s | !s.is_empty() ) - .clone() - ; - - let mut links = entry.links - .iter() - .map( | link | link.href.clone() ) - .clone() - ; - - let categories = entry.categories - .iter() - .map( | cat | cat.term.clone() ) - .collect::< Vec< _ > >() - ; - - let media = entry.media - .iter() - .map( | m | m.content.clone() ) - .flatten() - .filter_map( | m | m.url.map( | url | url.to_string() ) ) - .collect::< Vec< _ > >() - ; - - Frame - { - id : entry.id, - title : entry.title.map( | title | title.content ).clone(), - updated : entry.updated.clone(), - authors : ( !authors.is_empty() ).then( || authors.join( ", " ) ), - // qqq : why join? - content, - links : ( !links.len() == 0 ).then( || links.join( ", " ) ), - // qqq : why join? - summary : entry.summary.map( | c | c.content ).clone(), - categories : ( !categories.is_empty() ).then( || categories.join( ", " ) ), - // qqq : why join? - published : entry.published.clone(), - source : entry.source.clone(), - rights : entry.rights.map( | r | r.content ).clone(), - media : ( !media.is_empty() ).then( || media.join( ", " ) ), - // qqq : why join? - language : entry.language.clone(), - feed_link, - } - } -} - -/// Frames storing and retrieving. -#[ async_trait::async_trait( ?Send ) ] -pub trait FrameStore -{ - /// Insert items from list into feed table. - async fn save_frames( &mut self, feed : Vec< Frame > ) -> Result< Payload >; - - /// Update items from list in feed table. - async fn update_frames( &mut self, feed : Vec< Frame > ) -> Result< () >; - - /// Get all feed frames from storage. - async fn list_frames( &mut self ) -> Result< ListReport >; -} -// qqq : what is update? what update? don't use word update without noun and explanation what deos it mean - -#[ async_trait::async_trait( ?Send ) ] -impl FrameStore for FeedStorage< SledStorage > -{ - async fn list_frames( &mut self ) -> Result< ListReport > - { - let res = table( "frame" ).select().execute( &mut *self.storage.lock().await ).await?; - - let mut reports = Vec::new(); - let all_frames = match res - { - Payload::Select { labels: label_vec, rows: rows_vec } => - { - SelectedEntries - { - selected_rows : rows_vec, - selected_columns : label_vec, - } - }, - _ => SelectedEntries::new(), - }; - - let mut feeds_map = HashMap::new(); - - for row in all_frames.selected_rows - { - let title_val = row.last().unwrap().clone(); - let title = String::from( title_val ); - feeds_map.entry( title ) - .and_modify( | vec : &mut Vec< Vec< Value > > | vec.push( row.clone() ) ) - .or_insert( vec![ row ] ) - ; - } - - for ( title, frames ) in feeds_map - { - let mut report = FramesReport::new( title ); - report.existing_frames = frames.len(); - report.selected_frames = SelectedEntries - { - selected_rows : frames, - selected_columns : all_frames.selected_columns.clone(), - }; - reports.push( report ); - } - - Ok( ListReport( reports ) ) - } - - async fn save_frames( &mut self, frames : Vec< Frame > ) -> Result< Payload > - { - let entries_rows : Vec< Vec< ExprNode< 'static > > > = frames.into_iter().map( | entry | entry.into() ).collect_vec(); - - let insert = table( "frame" ) - .insert() - .columns - ( - self.frame_fields.iter().map( | field | field[ 0 ] ).join( "," ).as_str() - ) - .values( entries_rows ) - .execute( &mut *self.storage.lock().await ) - .await - .context( "Failed to insert frames" )? - ; - - Ok( insert ) - } - - async fn update_frames( &mut self, feed : Vec< Frame > ) -> Result< () > - { - let entries_rows : Vec< Vec< ExprNode< 'static > > > = feed.into_iter().map( | entry | entry.into() ).collect_vec(); - - for entry in entries_rows - { - let _update = table( "frame" ) - .update() - .set( "title", entry[ 1 ].to_owned() ) - .set( "content", entry[ 4 ].to_owned() ) - .set( "links", entry[ 5 ].to_owned() ) - .set( "summary", entry[ 6 ].to_owned() ) - .set( "published", entry[ 8 ].to_owned() ) - .set( "media", entry[ 9 ].to_owned() ) - .filter( col( "id" ).eq( entry[ 0 ].to_owned() ) ) - .execute( &mut *self.storage.lock().await ) - .await - .context( "Failed to update frames" )? - ; - } - Ok( () ) - } -} - -// qqq : what is it for and why? -impl From< Frame > for Vec< ExprNode< 'static > > -{ - fn from( entry : Frame ) -> Self - { - let title = entry.title - .map( | title | text( title ) ) - .unwrap_or( null() ) - ; - - let updated = entry.updated - .map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ) - .unwrap_or( null() ) - ; - - let authors = entry.authors - .map( | authors | text( authors ) ) - .unwrap_or( null() ) - ; - - let content = entry.content - .map( | content | text ( content ) ) - .unwrap_or( null() ) - ; - - let links = entry.links - .map( | links | text ( links ) ) - .unwrap_or( null() ) - ; - - let summary = entry.summary - .map( | summary | text ( summary ) ) - .unwrap_or( null() ) - ; - - let categories = entry.categories - .map( | categories | text ( categories ) ) - .unwrap_or( null() ) - ; - - let published = entry.published - .map( | d | timestamp( d.to_rfc3339_opts( SecondsFormat::Millis, true ) ) ) - .unwrap_or( null() ) - ; - - let source = entry.source.map( | s | text( s ) ).unwrap_or( null() ); - let rights = entry.rights.map( | r | text( r ) ).unwrap_or( null() ); - let media = entry.media - .map( | media | text ( media ) ) - .unwrap_or( null() ) - ; - - let language = entry.language.clone().map( | l | text( l ) ).unwrap_or( null() ); - - vec! - [ - text( entry.id ), - title, - updated, - authors, - content, - links, - summary, - categories, - published, - source, - rights, - media, - language, - text( entry.feed_link ) - ] - } -} - -// qqq : RowValue or CellValue? -/// GlueSQL Value wrapper for display. -#[ derive( Debug ) ] -pub struct RowValue< 'a >( pub &'a gluesql::prelude::Value ); - -impl std::fmt::Display for RowValue< '_ > -{ - fn fmt( &self, f : &mut std::fmt::Formatter<'_> ) -> std::fmt::Result - { - use gluesql::prelude::Value::*; - match &self.0 - { - Bool( val ) => write!( f, "{}", val )?, - I8( val ) => write!( f, "{}", val )?, - I16( val ) => write!( f, "{}", val )?, - I32( val ) => write!( f, "{}", val )?, - I64( val ) => write!( f, "{}", val )?, - I128( val ) => write!( f, "{}", val )?, - U8( val ) => write!( f, "{}", val )?, - U16( val ) => write!( f, "{}", val )?, - U32( val ) => write!( f, "{}", val )?, - U64( val ) => write!( f, "{}", val )?, - U128( val ) => write!( f, "{}", val )?, - F32( val ) => write!( f, "{}", val )?, - F64( val ) => write!( f, "{}", val )?, - Str( val ) => write!( f, "{}", val )?, - Null => write!( f, "Null" )?, - Timestamp( val ) => write!( f, "{}", val )?, - _ => write!( f, "" )?, - } - - Ok( () ) - } -} - -impl From< RowValue< '_ > > for String -{ - fn from( value : RowValue< '_ > ) -> Self - { - use gluesql::core::data::Value::*; - match &value.0 - { - Str( val ) => val.clone(), - _ => String::new(), - } - } -} diff --git a/module/move/unitore/src/storage/mod.rs b/module/move/unitore/src/storage/mod.rs deleted file mode 100644 index 1eedc29afd..0000000000 --- a/module/move/unitore/src/storage/mod.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Storage for frames, feeds and config files. - -use crate::*; -use std::sync::Arc; -use error_tools::{ for_app::Context, Result }; -use tokio::sync::Mutex; -use gluesql:: -{ - core:: - { - ast_builder::{ table, Build, Execute }, - store::{ GStore, GStoreMut }, - }, - prelude::Glue, - sled_storage::{ sled::Config, SledStorage }, -}; - -use executor::actions::query::QueryReport; - -pub mod config; -pub mod frame; -pub mod table; -pub mod feed; - -/// Storage for feed frames. -#[ derive( Clone ) ] -pub struct FeedStorage< S : GStore + GStoreMut + Send > -{ - /// GlueSQL storage. - pub storage : Arc< Mutex< Glue< S > > >, - frame_fields : Vec< [ &'static str; 3 ] >, -} - -impl< S : GStore + GStoreMut + Send > std::fmt::Debug for FeedStorage< S > -{ - fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result - { - writeln!(f, "GlueSQL storage" ) - } -} - -impl FeedStorage< SledStorage > -{ - /// Initialize new storage from configuration, create feed table. - pub async fn init_storage( config : Config ) -> Result< Self > - { - let storage = SledStorage::try_from( config.clone() ) - .context( format!( "Failed to initialize storage with config {:?}", config ) )? - ; - - let mut glue = Glue::new( storage ); - - let sub_table = table( "config" ) - .create_table_if_not_exists() - .add_column( "path TEXT PRIMARY KEY" ) - .build()? - ; - - sub_table.execute( &mut glue ).await?; - - let feed_table = table( "feed" ) - .create_table_if_not_exists() - .add_column( "link TEXT PRIMARY KEY" ) - .add_column( "type TEXT" ) - .add_column( "title TEXT" ) - .add_column( "updated TIMESTAMP" ) - .add_column( "authors TEXT" ) - .add_column( "description TEXT" ) - .add_column( "published TIMESTAMP" ) - .add_column( "update_period TEXT" ) - .build()? - ; - - feed_table.execute( &mut glue ).await?; - - let frame_fields = vec! - [ - [ "id", "TEXT", "A unique identifier for this frame in the feed. " ], - [ "title", "TEXT", "Title of the frame" ], - [ "updated", "TIMESTAMP", "Time at which this item was fetched from source." ], - [ "authors", "TEXT", "List of authors of the frame, optional." ], - [ "content", "TEXT", "The content of the frame in html or plain text, optional." ], - [ "links", "TEXT", "List of links associated with this item of related Web page and attachments." ], - [ "summary", "TEXT", "Short summary, abstract, or excerpt of the frame item, optional." ], - [ "categories", "TEXT", "Specifies a list of categories that the item belongs to." ], - [ "published", "TIMESTAMP", "Time at which this item was first published or updated." ], - [ "source", "TEXT", "Specifies the source feed if the frame was copied from one feed into another feed, optional." ], - [ "rights", "TEXT", "Conveys information about copyrights over the feed, optional." ], - [ "media", "TEXT", "List of media oblects, encountered in the frame, optional." ], - [ "language", "TEXT", "The language specified on the item, optional." ], - [ "feed_link", "TEXT", "Link of feed that contains this frame." ], - ]; - let mut table = table( "frame" ).create_table_if_not_exists().add_column( "id TEXT PRIMARY KEY" ); - - for column in frame_fields.iter().skip( 1 ).take( frame_fields.len() - 2 ) - { - table = table.add_column( format!( "{} {}", column[ 0 ], column[ 1 ] ).as_str() ); - } - - let table = table.add_column( "feed_link TEXT FOREIGN KEY REFERENCES feed(link)" ) - .build()? - ; - - table.execute( &mut glue ).await?; - - Ok( Self{ storage : Arc::new( Mutex::new( glue ) ), frame_fields } ) - } -} - -/// Functionality of feed storage. -#[ mockall::automock ] -#[ async_trait::async_trait( ?Send ) ] -pub trait Store -{ - /// Execute custom query passed as String. - async fn execute_query( &mut self, query : String ) -> Result< QueryReport >; -} - -#[ async_trait::async_trait( ?Send ) ] -impl< S : GStore + GStoreMut + Send > Store for FeedStorage< S > -{ - async fn execute_query( &mut self, query : String ) -> Result< QueryReport > - { - let glue = &mut *self.storage.lock().await; - let payloads = glue.execute( &query ).await.context( "Failed to execute query" )?; - - let report = QueryReport ( payloads ); - - Ok( report ) - } -} diff --git a/module/move/unitore/src/storage/table.rs b/module/move/unitore/src/storage/table.rs deleted file mode 100644 index 08038df8ee..0000000000 --- a/module/move/unitore/src/storage/table.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Tables sroring functions. - -use crate::*; -use error_tools::Result; -use gluesql:: -{ - sled_storage::SledStorage, - prelude::Payload, -}; - -use executor::actions::table::TablesReport; -use storage::FeedStorage; - -/// Functions for tables informantion. -#[ async_trait::async_trait( ?Send ) ] -pub trait TableStore -{ - /// List tables in storage. - async fn list_tables( &mut self ) -> Result< TablesReport >; - - /// List columns of table. - async fn list_columns( &mut self, table_name : String ) -> Result< Vec< Payload > >; -} - -#[ async_trait::async_trait( ?Send ) ] -impl TableStore for FeedStorage< SledStorage > -{ - async fn list_tables( &mut self ) -> Result< TablesReport > - { - let glue = &mut *self.storage.lock().await; - let payloads = glue.execute( "SELECT * FROM GLUE_TABLE_COLUMNS" ).await?; - - let report = TablesReport::new( payloads ); - - Ok( report ) - } - - async fn list_columns( &mut self, table_name : String ) -> Result< Vec< Payload > > - { - let glue = &mut *self.storage.lock().await; - let query_str = format!( "SELECT * FROM GLUE_TABLE_COLUMNS WHERE TABLE_NAME='{}'", table_name ); - let payloads = glue.execute( &query_str ).await?; - - Ok( payloads ) - } - -} diff --git a/module/move/unitore/src/tool/mod.rs b/module/move/unitore/src/tool/mod.rs new file mode 100644 index 0000000000..749ba59a0e --- /dev/null +++ b/module/move/unitore/src/tool/mod.rs @@ -0,0 +1,3 @@ +//! Tools for additional functionality. + +pub mod table_display; \ No newline at end of file diff --git a/module/move/unitore/src/table_display.rs b/module/move/unitore/src/tool/table_display.rs similarity index 62% rename from module/move/unitore/src/table_display.rs rename to module/move/unitore/src/tool/table_display.rs index 9f334cc8ee..4b5f35475a 100644 --- a/module/move/unitore/src/table_display.rs +++ b/module/move/unitore/src/tool/table_display.rs @@ -1,12 +1,16 @@ -//! Helper for command report representation. +//! Wrapper for command report representation. +//! Separates usage of cli-table library behind facade for convenient changes in future. use cli_table:: { - format::{ Border, Separator }, Cell, Style, Table, TableDisplay + format::{ Border, HorizontalLine, Separator }, Cell, Style, Table, TableDisplay }; // qqq : purpose well defined should be always be in documentation -/// Wrapper struct for cli-table table with iplementation of Display. +// aaa : added explanation + +/// Wrapper struct for cli-table table with implementation of Display. +/// Separates usage of cli-table library behind facade for convenient changes in future. pub struct ReportTable( TableDisplay ); impl std::fmt::Display for ReportTable @@ -63,5 +67,22 @@ pub fn table_with_headers( headers : Vec< String >, rows : Vec< Vec< String > > .separator( Separator::builder().build() ) ; + table_struct.display().map( | table | ReportTable( table ) ).ok() +} + +/// Transform 2-dimensional vec of String data into displayable table with plain rows and bottom border. +pub fn plain_with_border( rows : Vec< Vec< String > > ) -> Option< ReportTable > +{ + let rows = rows + .into_iter() + .map( | row | row.into_iter().map( | cell_val | cell_val.cell() ).collect::< Vec< _ > >() ) + .collect::< Vec< _ > >() + ; + + let table_struct = rows.table() + .border( Border::builder().bottom(HorizontalLine::default()).build() ) + .separator( Separator::builder().build() ) + ; + table_struct.display().map( | table | ReportTable( table ) ).ok() } \ No newline at end of file diff --git a/module/move/unitore/tests/add_config.rs b/module/move/unitore/tests/add_config.rs deleted file mode 100644 index 24e83d0d8a..0000000000 --- a/module/move/unitore/tests/add_config.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::path::PathBuf; - -use gluesql::sled_storage::sled::Config; -use unitore:: -{ - executor::FeedManager, - storage::{ FeedStorage, feed::FeedStore }, -}; -use error_tools::Result; - -#[ tokio::test ] -async fn add_config_file() -> Result< () > -{ - let path = PathBuf::from( "./tests/fixtures/test_config.toml" ); - let path = path.canonicalize().expect( "Invalid path" ); - - let config = Config::default() - .path( "./test".to_owned() ) - .temporary( true ) - ; - - let feed_storage = FeedStorage::init_storage( config ).await?; - unitore::executor::actions::config::add_config( feed_storage.clone(), &wca::Args( vec![ wca::Value::Path( path ) ] ) ).await?; - - let mut manager = FeedManager::new( feed_storage ); - let res = manager.storage.get_all_feeds().await?; - - let feeds_links = res.0.selected_rows - .iter() - .map( | feed | String::from( feed[ 1 ].clone() ) ) - .collect::< Vec< _ > >() - ; - - assert!( feeds_links.len() == 2 ); - assert!( feeds_links.contains( &format!( "https://feeds.bbci.co.uk/news/world/rss.xml" ) ) ); - assert!( feeds_links.contains( &format!( "https://rss.nytimes.com/services/xml/rss/nyt/World.xml" ) ) ); - - Ok( () ) -} diff --git a/module/move/unitore/tests/frame.rs b/module/move/unitore/tests/basic.rs similarity index 59% rename from module/move/unitore/tests/frame.rs rename to module/move/unitore/tests/basic.rs index 248f40330b..6e5df1ad4d 100644 --- a/module/move/unitore/tests/frame.rs +++ b/module/move/unitore/tests/basic.rs @@ -5,13 +5,9 @@ use error_tools::Result; async fn frame() -> Result< () > { let feed = feed_parser::parse( include_str!( "./fixtures/plain_feed.xml" ).as_bytes() )?; - - let frame = unitore::storage::frame::Frame::from( ( feed.entries[ 0 ].clone(), String::new() ) ); - + let frame = unitore::entity::frame::Frame::from( ( feed.entries[ 0 ].clone(), String::new() ) ); assert!( frame.id == feed.entries[ 0 ].id ); - println!( "{:#?}", feed.entries[ 0 ].media ); - println!( "{:#?}", frame ); Ok( () ) } diff --git a/module/move/unitore/tests/config_add.rs b/module/move/unitore/tests/config_add.rs new file mode 100644 index 0000000000..455a77dbd4 --- /dev/null +++ b/module/move/unitore/tests/config_add.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; +use gluesql::sled_storage::sled::Config; +use unitore:: +{ + sled_adapter::FeedStorage, + entity::feed::FeedStore, + action::config, +}; +use error_tools::Result; + +#[ tokio::test ] +async fn config_add() -> Result< () > +{ + let path = PathBuf::from( "./tests/fixtures/test_config.toml" ); + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + config::config_add( feed_storage.clone(), &path ).await?; + + let res = feed_storage.feeds_list().await?; + + let feeds_links = res.0.selected_rows + .iter() + .map( | feed | String::from( feed[ 1 ].clone() ) ) + .collect::< Vec< _ > >() + ; + + assert!( feeds_links.len() == 1 ); + assert!( feeds_links.contains( &format!( "https://www.nasa.gov/feed/" ) ) ); + + Ok( () ) +} diff --git a/module/move/unitore/tests/config_delete.rs b/module/move/unitore/tests/config_delete.rs new file mode 100644 index 0000000000..63a792725e --- /dev/null +++ b/module/move/unitore/tests/config_delete.rs @@ -0,0 +1,43 @@ +use gluesql:: +{ + sled_storage::sled::Config, + prelude::Payload::Select, +}; +use unitore:: +{ + sled_adapter::FeedStorage, + entity::config::ConfigStore, + action::config, +}; +use error_tools::Result; + +#[ tokio::test ] +async fn config_delete() -> Result< () > +{ + + let path = std::path::PathBuf::from( "./tests/fixtures/test_config.toml" ); + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + config::config_add( feed_storage.clone(), &path ).await?; + + config::config_delete( feed_storage.clone(), &path ).await?; + + let list = feed_storage.config_list().await?; + + if let Select{ labels : _, rows } = list + { + assert!( rows.len() == 0 ) + } + else + { + assert!( false ); + } + + Ok( () ) +} diff --git a/module/move/unitore/tests/fixtures/plain_feed.xml b/module/move/unitore/tests/fixtures/plain_feed.xml index 53c32e9fd1..7048caabd0 100644 --- a/module/move/unitore/tests/fixtures/plain_feed.xml +++ b/module/move/unitore/tests/fixtures/plain_feed.xml @@ -69,7 +69,7 @@ - + @@ -328,7 +328,7 @@ Thu, 14 Mar 2024 13:00:00 +0000 - + diff --git a/module/move/unitore/tests/fixtures/test_config.toml b/module/move/unitore/tests/fixtures/test_config.toml index ed8606eb53..586e2f3787 100644 --- a/module/move/unitore/tests/fixtures/test_config.toml +++ b/module/move/unitore/tests/fixtures/test_config.toml @@ -1,7 +1,3 @@ [[config]] update_period = "1min" -link = "https://feeds.bbci.co.uk/news/world/rss.xml" - -[[config]] -update_period = "1min" -link = "https://rss.nytimes.com/services/xml/rss/nyt/World.xml" \ No newline at end of file +link = "https://www.nasa.gov/feed/" diff --git a/module/move/unitore/tests/frames_download.rs b/module/move/unitore/tests/frames_download.rs new file mode 100644 index 0000000000..ae8119a71d --- /dev/null +++ b/module/move/unitore/tests/frames_download.rs @@ -0,0 +1,121 @@ +use feed_rs::parser as feed_parser; +use gluesql:: +{ + core:: + { + chrono::{ DateTime, Utc }, + data::Value + }, + sled_storage::sled::Config, +}; +use wca::wtools::Itertools; +use unitore:: +{ + feed_config::SubscriptionConfig, + sled_adapter::FeedStorage, + entity::{ frame::FrameStore, feed::FeedStore }, +}; +use error_tools::Result; + +#[ tokio::test ] +async fn test_save() -> Result< () > +{ + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + + let feed_config = SubscriptionConfig + { + update_period : std::time::Duration::from_secs( 1000 ), + link : url::Url::parse( "https://www.nasa.gov/feed/" )?, + }; + + let mut feeds = Vec::new(); + + let feed = feed_parser::parse( include_str!("./fixtures/plain_feed.xml").as_bytes() )?; + feeds.push( ( feed, feed_config.update_period.clone(), feed_config.link.clone() ) ); + feed_storage.feeds_process( feeds ).await?; + + let entries = feed_storage.frames_list().await?; + + let number_of_frames = entries.0[ 0 ].selected_frames.selected_rows.len(); + assert_eq!( number_of_frames, 10 ); + + Ok( () ) +} + +#[ tokio::test ] +async fn test_update() -> Result< () > +{ + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + + let feed_config = SubscriptionConfig + { + update_period : std::time::Duration::from_secs( 1000 ), + link : url::Url::parse( "https://www.nasa.gov/feed/" )?, + }; + + // initial fetch + let feed = feed_parser::parse( include_str!("./fixtures/plain_feed.xml").as_bytes() )?; + let feeds = vec![ ( feed, feed_config.update_period.clone(), feed_config.link.clone() ) ]; + feed_storage.feeds_process( feeds ).await?; + + // updated fetch + let feed = feed_parser::parse( include_str!("./fixtures/updated_one_frame.xml").as_bytes() )?; + + let feeds = vec![ ( feed, feed_config.update_period.clone(), feed_config.link.clone() ) ]; + feed_storage.feeds_process( feeds ).await?; + + // check + let payload = feed_storage.frames_list().await?; + + let entries = payload.0 + .iter() + .map( | val | val.selected_frames.selected_rows.clone() ) + .flatten() + .collect::< Vec< _ > >() + ; + + let entries = entries.iter().map( | entry | + { + let id = match &entry[ 0 ] + { + Value::Str( s ) => s.to_owned(), + _ => String::new(), + }; + + let published = match &entry[ 8 ] + { + Value::Timestamp( date_time ) => date_time.and_utc(), + _ => DateTime::< Utc >::default(), + }; + ( id, published ) + } + ) + .collect_vec() + ; + + // no duplicates + assert_eq!( entries.len(), 10 ); + + // check date + let updated = entries.iter().find + ( + | ( id, _published ) | id == "https://www.nasa.gov/?post_type=image-article&p=631537" + ); + assert!( updated.is_some() ); + let _updated = updated.unwrap(); + Ok( () ) +} diff --git a/module/move/unitore/tests/query_execute.rs b/module/move/unitore/tests/query_execute.rs new file mode 100644 index 0000000000..7741575322 --- /dev/null +++ b/module/move/unitore/tests/query_execute.rs @@ -0,0 +1,187 @@ +use feed_rs::parser as feed_parser; +use unitore:: +{ + feed_config::SubscriptionConfig, + sled_adapter::{ FeedStorage, Store, MockStore }, + entity::{ config::ConfigStore, feed::FeedStore }, + action::{ query::{ self, QueryReport }, config }, + command::query::QueryCommand, +}; +use gluesql:: +{ + prelude::{ Payload::{ self, Select }, Value::{ Str, Timestamp } }, + core::chrono::NaiveDateTime, + sled_storage::sled, +}; +use wca::{ VerifiedCommand, CommandsAggregator, Type, Parser, Dictionary, Verifier, Executor }; +use error_tools::Result; +use mockall::predicate; +use std::path::PathBuf; + +#[ test ] +fn query_execute() -> Result< () > +{ + // init parser + let parser = Parser; + + // init converter + let dictionary = &Dictionary::former() + .command( QueryCommand::execute()? ) + .form() + ; + let verifier = Verifier; + + // init executor + let executor = Executor::former().form(); + let args = vec![ ".query.execute".to_string(), "SELECT title FROM frame".into() ]; + let raw_program = parser.parse( args ).unwrap(); + let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); + + let res = executor.program( dictionary, grammar_program ); + assert!( res.is_ok() ); + + // test action + let rt = tokio::runtime::Runtime::new()?; + let ca = CommandsAggregator::former() + .command( "query.execute" ) + .hint( "hint" ) + .long_hint( "long_hint" ) + .subject().hint( "SQL query" ).kind( Type::String ).optional( false ).end() + .routine( move | o : VerifiedCommand | + { + let mut f_store = MockStore::new(); + f_store + .expect_query_execute() + .with( predicate::eq( "SELECT title FROM frame".to_string() ) ) + .times( 1 ) + .returning( | _ | Ok( QueryReport + ( + vec! + [ + Select { labels : vec![ Str( "title".to_string() ).into() ], rows : Vec::new() } + ] + ) + ) ) + ; + _ = rt.block_on( async move + { + let query_arg = o.args + .get_owned::< String >( 0 ) + ; + + let query_str = query_arg.unwrap(); + query::query_execute( f_store, query_str ).await + } ); + } ) + .end() + .perform(); + let entries = ca.perform( vec![ ".query.execute".to_string(), "SELECT title FROM frame".into() ] ); + assert!( entries.is_ok() ); + Ok( () ) +} + +#[ tokio::test ] +async fn query_feeds() -> Result< () > +{ + let path = PathBuf::from( "./tests/fixtures/test_config.toml" ); + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = sled::Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + config::config_add( feed_storage.clone(), &path ).await?; + + let entries = feed_storage.query_execute( "SELECT link FROM feed".to_string() ).await?; + + assert!( !entries.0.is_empty() ); + if let Select { labels, rows } = &entries.0[ 0 ] + { + assert_eq!( labels.len(), 1 ); + assert_eq!( labels[ 0 ], "link" ); + assert_eq!( rows.len(), 1 ); + } + else + { + assert!( false ) + } + + Ok( () ) +} + +#[ tokio::test ] +async fn query_frames() -> Result< () > +{ + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = sled::Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + let feed_config = SubscriptionConfig + { + update_period : std::time::Duration::from_secs( 1000 ), + link : url::Url::parse( "https://www.nasa.gov/feed/" )?, + }; + let mut feeds = Vec::new(); + + let feed = feed_parser::parse( include_str!("./fixtures/plain_feed.xml").as_bytes() )?; + feeds.push( ( feed, feed_config.update_period.clone(), feed_config.link.clone() ) ); + feed_storage.feeds_process( feeds ).await?; + + let entries = feed_storage.query_execute( "SELECT title, published FROM frame ORDER BY published".to_string() ).await?; + + assert!( !entries.0.is_empty() ); + + if let Select { labels, rows } = &entries.0[ 0 ] + { + assert_eq!( labels.len(), 2 ); + assert!( labels.contains( &String::from( "title" ) ) ); + assert!( labels.contains( &String::from( "published" ) ) ); + assert_eq!( rows.len(), 10 ); + assert_eq!( rows[ 0 ][ 0 ], Str( "8 Must-Have NASA Resources for Science Teachers in 2024".to_string() ) ); + assert_eq!( rows[ 0 ][ 1 ], Timestamp( NaiveDateTime::parse_from_str( "13 Mar 2024 16:31:23", "%d %b %Y %H:%M:%S" )? ) ); + assert_eq!( rows[ 9 ][ 0 ], Str( "Icing Cloud Characterization Engineer Emily Timko".to_string() ) ); + assert_eq!( rows[ 9 ][ 1 ], Timestamp( NaiveDateTime::parse_from_str( "14 Mar 2024 14:27:52", "%d %b %Y %H:%M:%S" )? ) ); + } + else + { + assert!( false ) + } + + Ok( () ) +} + +#[ tokio::test ] +async fn query_configs() -> Result< () > +{ + let path = PathBuf::from( "./tests/fixtures/test_config.toml" ); + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = sled::Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + let _res = feed_storage.query_execute( format!( "INSERT INTO config VALUES ('{}') ", path.to_string_lossy().to_string() ) ).await?; + let res = feed_storage.config_list().await?; + + if let Payload::Select{ labels, rows } = &res + { + assert_eq!( labels.len(), 1 ); + assert!( labels.contains( &String::from( "path" ) ) ); + assert_eq!( rows.len(), 1 ); + assert_eq!( rows[ 0 ][ 0 ], Str( path.to_string_lossy().to_string() ) ); + } + else + { + assert!( false ); + } + + Ok( () ) +} diff --git a/module/move/unitore/tests/save_feed.rs b/module/move/unitore/tests/save_feed.rs deleted file mode 100644 index e6b20c18b6..0000000000 --- a/module/move/unitore/tests/save_feed.rs +++ /dev/null @@ -1,72 +0,0 @@ -use async_trait::async_trait; -use feed_rs::parser as feed_parser; -use unitore:: -{ - feed_config::SubscriptionConfig, - retriever::FeedFetch, - storage::{ FeedStorage, frame::FrameStore, feed::FeedStore }, -}; -use error_tools::Result; - -/// Feed client for testing. -#[derive(Debug)] -pub struct TestClient; - -#[ async_trait ] -impl FeedFetch for TestClient -{ - async fn fetch( &self, _ : url::Url ) -> Result< feed_rs::model::Feed > - { - let feed = feed_parser::parse( include_str!( "./fixtures/plain_feed.xml" ).as_bytes() )?; - - Ok( feed ) - } -} - -#[ tokio::test ] -async fn test_save_feed_plain() -> Result< () > -{ - // let mut f_store = MockFeedStore::new(); - // f_store - // .expect_process_feeds() - // .times( 1 ) - // .returning( | _ | Ok( UpdateReport( - // vec! [ FramesReport - // { - // new_frames : 2, - // updated_frames : 0, - // selected_frames : SelectedEntries::new(), - // existing_frames : 0, - // feed_link : String::new(), - // is_new_feed : false, - // } ] ) ) ) - // ; - - let config = gluesql::sled_storage::sled::Config::default() - .path( "./test".to_owned() ) - .temporary( true ) - ; - - let mut feed_storage = FeedStorage::init_storage( config ).await?; - - let feed_config = SubscriptionConfig - { - update_period : std::time::Duration::from_secs( 1000 ), - link : url::Url::parse( "https://www.nasa.gov/feed/" )?, - }; - - let mut feeds = Vec::new(); - let client = TestClient; - - let feed = FeedFetch::fetch( &client, feed_config.link.clone()).await?; - feeds.push( ( feed, feed_config.update_period.clone(), feed_config.link.clone() ) ); - feed_storage.process_feeds( feeds ).await?; - - let entries = feed_storage.list_frames().await?; - - let number_of_frames = entries.0[ 0 ].selected_frames.selected_rows.len(); - - assert_eq!( number_of_frames, 10 ); - - Ok( () ) -} diff --git a/module/move/unitore/tests/table_list.rs b/module/move/unitore/tests/table_list.rs new file mode 100644 index 0000000000..188d1a3131 --- /dev/null +++ b/module/move/unitore/tests/table_list.rs @@ -0,0 +1,47 @@ +use gluesql:: +{ + sled_storage::sled::Config, + prelude::{ Payload, Value::Str }, +}; +use unitore:: +{ + sled_adapter::FeedStorage, + entity::table::TableStore, +}; +use error_tools::Result; + +#[ tokio::test ] +async fn table_list() -> Result< () > +{ + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + let res = feed_storage.table_list( String::from( "feed" ) ).await?; + + if let Payload::Select { labels: _, rows } = &res[ 0 ] + { + let column_names = rows + .iter() + .map( | row | row[ 1 ].clone() ) + .collect::< Vec< _ > >() + ; + + assert_eq!( column_names.len(), 9 ); + assert!( column_names.contains( &Str( String::from( "published") ) ) ); + assert!( column_names.contains( &Str( String::from( "authors") ) ) ); + assert!( column_names.contains( &Str( String::from( "description") ) ) ); + assert!( column_names.contains( &Str( String::from( "type") ) ) ); + assert!( column_names.contains( &Str( String::from( "title") ) ) ); + assert!( column_names.contains( &Str( String::from( "updated") ) ) ); + assert!( column_names.contains( &Str( String::from( "link") ) ) ); + assert!( column_names.contains( &Str( String::from( "update_period" ) ) ) ); + assert!( column_names.contains( &Str( String::from( "config_file" ) ) ) ); + } + + Ok( () ) +} diff --git a/module/move/unitore/tests/tables_list.rs b/module/move/unitore/tests/tables_list.rs new file mode 100644 index 0000000000..0972041704 --- /dev/null +++ b/module/move/unitore/tests/tables_list.rs @@ -0,0 +1,34 @@ +use gluesql::sled_storage::sled::Config; +use unitore:: +{ + sled_adapter::FeedStorage, + entity::table::TableStore, +}; +use error_tools::Result; + +#[ tokio::test ] +async fn tables_list() -> Result< () > +{ + let temp_path = proper_path_tools::path::unique_folder_name().unwrap(); + + let config = Config::default() + .path( format!( "./{}", temp_path ) ) + .temporary( true ) + ; + + let mut feed_storage = FeedStorage::init_storage( &config ).await?; + let res = feed_storage.tables_list().await?; + + let table_names = res.0 + .iter() + .map( | ( table_name, _info ) | table_name ) + .collect::< Vec< _ > >() + ; + + assert_eq!( table_names.len(), 3 ); + assert!( table_names.contains( &&String::from( "config") ) ); + assert!( table_names.contains( &&String::from( "feed" ) ) ); + assert!( table_names.contains( &&String::from( "frame" ) ) ); + + Ok( () ) +} diff --git a/module/move/unitore/tests/update_newer_feed.rs b/module/move/unitore/tests/update_newer_feed.rs deleted file mode 100644 index 324ed68556..0000000000 --- a/module/move/unitore/tests/update_newer_feed.rs +++ /dev/null @@ -1,98 +0,0 @@ -use async_trait::async_trait; -use feed_rs::parser as feed_parser; -use gluesql:: -{ - core:: - { - chrono::{ DateTime, Utc }, - data::Value - }, - sled_storage::sled::Config, -}; -use unitore:: -{ - feed_config::SubscriptionConfig, - retriever::FeedFetch, - storage::{ feed::FeedStore, frame::FrameStore, FeedStorage }, -}; -use wca::wtools::Itertools; -use error_tools::Result; - -/// Feed client for testing. -#[derive(Debug)] -pub struct TestClient ( String ); - -#[ async_trait ] -impl FeedFetch for TestClient -{ - async fn fetch( &self, _ : url::Url ) -> Result< feed_rs::model::Feed > - { - let feed = feed_parser::parse( std::fs::read_to_string( &self.0 )?.as_bytes() )?; - Ok( feed ) - } -} - -#[ tokio::test ] -async fn test_update() -> Result< () > -{ - let config = Config::default() - .path( "./test".to_owned() ) - .temporary( true ) - ; - - let mut feed_storage = FeedStorage::init_storage( config ).await?; - - let feed_config = SubscriptionConfig - { - update_period : std::time::Duration::from_secs( 1000 ), - link : url::Url::parse( "https://www.nasa.gov/feed/" )?, - }; - - // initial fetch - let client = TestClient( "./tests/fixtures/plain_feed.xml".to_owned() ); - - let feed = FeedFetch::fetch( &client, feed_config.link.clone()).await?; - let feeds = vec![ ( feed, feed_config.update_period.clone(), feed_config.link.clone() ) ]; - feed_storage.process_feeds( feeds ).await?; - - // updated fetch - let client = TestClient( "./tests/fixtures/updated_one_frame.xml".to_owned() ); - - let feed = FeedFetch::fetch( &client, feed_config.link.clone()).await?; - let feeds = vec![ ( feed, feed_config.update_period.clone(), feed_config.link.clone() ) ]; - feed_storage.process_feeds( feeds ).await?; - - // check - let payload = feed_storage.list_frames().await?; - - let entries = payload.0.iter().map( | val | val.selected_frames.selected_rows.clone() ).flatten().collect::< Vec< _ > >(); - - let entries = entries.iter().map( | entry | - { - let id = match &entry[ 0 ] - { - Value::Str( s ) => s.to_owned(), - _ => String::new(), - }; - - let published = match &entry[ 8 ] - { - Value::Timestamp( date_time ) => date_time.and_utc(), - _ => DateTime::< Utc >::default(), - }; - ( id, published ) - } - ) - .collect_vec() - ; - - // no duplicates - assert_eq!( entries.len(), 10 ); - - // check date - println!( "{:?}", entries ); - let updated = entries.iter().find( | ( id, _published ) | id == "https://www.nasa.gov/?post_type=image-article&p=631537" ); - assert!( updated.is_some() ); - let _updated = updated.unwrap(); - Ok( () ) -} \ No newline at end of file diff --git a/module/move/wca/Cargo.toml b/module/move/wca/Cargo.toml index 9c418b4e7d..edabb9d0d6 100644 --- a/module/move/wca/Cargo.toml +++ b/module/move/wca/Cargo.toml @@ -31,7 +31,7 @@ full = [ "enabled", "on_unknown_suggest" ] enabled = [] # This configuration suggests an action to be done when the command is unknown. In this case, when an unknown command is encountered, the system might suggest alternatives -on_unknown_suggest = [ "eddie" ] +on_unknown_suggest = [ "dep:textdistance" ] [[bench]] name = "bench" @@ -47,11 +47,9 @@ iter_tools = { workspace = true, features = [ "default" ] } former = { workspace = true, features = [ "default" ] } ## external -anymap = "0.12" log = "0.4" -nom = "7.1" -closure = "0.3" -eddie = { version = "0.4", optional = true } # fuzzy commands search +#closure = "0.3" +textdistance = { version = "1.0", optional = true } # fuzzy commands search [dev-dependencies] test_tools = { workspace = true } diff --git a/module/move/wca/Readme.md b/module/move/wca/Readme.md index ecbad92af5..a9b9c0b4e4 100644 --- a/module/move/wca/Readme.md +++ b/module/move/wca/Readme.md @@ -2,8 +2,7 @@ # Module :: wca - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml)[![docs.rs](https://img.shields.io/docsrs/wca?color=e3e8f0&logo=docs.rs)](https://docs.rs/wca)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwca_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wca_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wca_push.yml) [![docs.rs](https://img.shields.io/docsrs/wca?color=e3e8f0&logo=docs.rs)](https://docs.rs/wca) [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=module/move/wca/examples/wca_trivial.rs,RUN_POSTFIX=--example%20/home/sakapoi/Документи/wTools_fork/module/move/wca/examples/wca_trivial/https://github.com/Wandalen/wTools) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) The tool to make CLI ( commands user interface ). It is able to aggregate external binary applications, as well as functions, which are written in your language. @@ -15,7 +14,7 @@ The tool to make CLI ( commands user interface ). It is able to aggregate extern ```rust #[ cfg( not( feature = "no_std" ) ) ] { - use wca::{ Args, Context, Type }; + use wca::{ VerifiedCommand, Context, Type }; fn main() { @@ -25,12 +24,12 @@ The tool to make CLI ( commands user interface ). It is able to aggregate extern .hint( "prints all subjects and properties" ) .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | args : Args, props | { println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ) } ) + .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) .end() .command( "error" ) .hint( "prints all subjects and properties" ) .subject().hint( "Error message" ).kind( Type::String ).optional( true ).end() - .routine( | args : Args | { println!( "Returns an error" ); Err( format!( "{}", args.get_owned::< String >( 0 ).unwrap_or_default() ) ) } ) + .routine( | o : VerifiedCommand | { println!( "Returns an error" ); Err( format!( "{}", o.args.get_owned::< String >( 0 ).unwrap_or_default() ) ) } ) .end() .command( "exit" ) .hint( "just exit" ) diff --git a/module/move/wca/examples/wca_fluent.rs b/module/move/wca/examples/wca_fluent.rs index 4de07f8ef4..cf60d4000c 100644 --- a/module/move/wca/examples/wca_fluent.rs +++ b/module/move/wca/examples/wca_fluent.rs @@ -7,24 +7,25 @@ //! -use wca::{ Args, Context, Type }; +use wca::{ Context, Type, VerifiedCommand }; use std::sync::{ Arc, Mutex }; fn main() { let ca = wca::CommandsAggregator::former() + .with_context( Mutex::new( 0 ) ) .command( "echo" ) .hint( "prints all subjects and properties" ) .subject().kind( Type::String ).optional( true ).end() .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | args : Args, props | { println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ) } ) + .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) .end() .command( "inc" ) .hint( "This command increments a state number each time it is called consecutively. (E.g. `.inc .inc`)" ) .routine( | ctx : Context | { - let i : Arc< Mutex< i32 > > = ctx.get_or_default(); + let i : Arc< Mutex< i32 > > = ctx.get().unwrap(); let mut i = i.lock().unwrap(); println!( "i = {}", i ); *i += 1; @@ -33,7 +34,7 @@ fn main() .command( "error" ) .hint( "prints all subjects and properties" ) .subject().kind( Type::String ).optional( true ).end() - .routine( | args : Args | { println!( "Returns an error" ); Err( format!( "{}", args.get_owned::< String >( 0 ).unwrap_or_default() ) ) } ) + .routine( | o : VerifiedCommand | { println!( "Returns an error" ); Err( format!( "{}", o.args.get_owned::< String >( 0 ).unwrap_or_default() ) ) } ) .end() .command( "exit" ) .hint( "just exit" ) diff --git a/module/move/wca/examples/wca_suggest.rs b/module/move/wca/examples/wca_suggest.rs index 43991979a6..2bb73fa111 100644 --- a/module/move/wca/examples/wca_suggest.rs +++ b/module/move/wca/examples/wca_suggest.rs @@ -20,7 +20,7 @@ //! ``` //! -use wca::{ CommandsAggregator, Args, Props, Type }; +use wca::{ CommandsAggregator, Type, VerifiedCommand }; fn main() { @@ -30,9 +30,9 @@ fn main() .hint( "prints all subjects and properties" ) .subject().kind( Type::String ).optional( true ).end() .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | args : Args, props : Props | + .routine( | o : VerifiedCommand | { - println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ); + println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ); }) .end() .perform(); diff --git a/module/move/wca/examples/wca_trivial.rs b/module/move/wca/examples/wca_trivial.rs index e1a27420ed..272923ecf5 100644 --- a/module/move/wca/examples/wca_trivial.rs +++ b/module/move/wca/examples/wca_trivial.rs @@ -2,11 +2,11 @@ //! A trivial example. //! -use wca::{ CommandsAggregator, Args, Props, Type }; +use wca::{ CommandsAggregator, Type, VerifiedCommand }; -fn f1( args : Args, props : Props ) +fn f1( o : VerifiedCommand ) { - println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ); + println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ); } fn exit() diff --git a/module/move/wca/src/ca/aggregator.rs b/module/move/wca/src/ca/aggregator.rs index c6e8f2f2ba..1174de448f 100644 --- a/module/move/wca/src/ca/aggregator.rs +++ b/module/move/wca/src/ca/aggregator.rs @@ -3,16 +3,21 @@ pub( crate ) mod private use crate::*; use ca:: { - Parser, Verifier, + Verifier, Executor, - ProgramParser, - Command, - grammar::command::private::CommandFormer, + grammar::command::private:: + { + CommandFormer, + CommandAsSubformer, + CommandAsSubformerEnd, + CommandFormerStorage + }, help::{ HelpGeneratorFn, HelpGeneratorOptions, HelpVariants }, }; use std::collections::HashSet; use std::fmt; + use former::StoragePreform; use wtools::thiserror; use wtools::error:: { @@ -78,7 +83,7 @@ pub( crate ) mod private /// # Example: /// /// ``` - /// use wca::{ CommandsAggregator, Args, Props, Type }; + /// use wca::{ CommandsAggregator, VerifiedCommand, Type }; /// /// # fn main() -> Result< (), Box< dyn std::error::Error > > { /// let ca = CommandsAggregator::former() @@ -86,7 +91,7 @@ pub( crate ) mod private /// .hint( "prints all subjects and properties" ) /// .subject().hint( "argument" ).kind( Type::String ).optional( false ).end() /// .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( false ).end() - /// .routine( | args : Args, props : Props | println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ) ) + /// .routine( | o : VerifiedCommand | println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) ) /// .end() /// .perform(); /// @@ -95,55 +100,70 @@ pub( crate ) mod private /// ``` #[ derive( Debug ) ] #[ derive( former::Former ) ] - #[ perform( fn build() -> CommandsAggregator ) ] + #[ storage_fields( help_generator : HelpGeneratorFn, help_variants : HashSet< HelpVariants > ) ] + #[ mutator( custom = true ) ] + // #[ debug ] pub struct CommandsAggregator { - #[ default( Dictionary::default() ) ] + #[ former( default = Dictionary::default() ) ] dictionary : Dictionary, - #[ default( Parser::former().form() ) ] + #[ former( default = Parser ) ] parser : Parser, - #[ setter( false ) ] - #[ default( Executor::former().form() ) ] + #[ scalar( setter = false, hint = false ) ] + #[ former( default = Executor::former().form() ) ] executor : Executor, - help_generator : Option< HelpGeneratorFn >, - #[ default( HashSet::from([ HelpVariants::All ]) ) ] - help_variants : HashSet< HelpVariants >, - // aaa : for Bohdan : should not have fields help_generator and help_variants - // help_generator generateds VerifiedCommand(s) and stop to exist - // aaa : Defaults after formation - - // #[ default( Verifier::former().form() ) ] - #[ default( Verifier ) ] + #[ former( default = Verifier ) ] verifier : Verifier, - // #[ default( ExecutorConverter::former().form() ) ] - // executor_converter : ExecutorConverter, - callback_fn : Option< CommandsAggregatorCallback >, } - impl< Context, End > CommandsAggregatorFormer< Context, End > + impl< Context, Formed > former::FormerMutator for CommandsAggregatorFormerDefinitionTypes< Context, Formed > + { + fn form_mutation( storage : &mut Self::Storage, _context : &mut Option< Self::Context > ) + { + let ca = storage; + let dictionary = ca.dictionary.get_or_insert_with( Dictionary::default ); + + let help_generator = std::mem::take( &mut ca.help_generator ).unwrap_or_default(); + let help_variants = std::mem::take( &mut ca.help_variants ).unwrap_or_else( || HashSet::from([ HelpVariants::All ]) ); + + if help_variants.contains( &HelpVariants::All ) + { + HelpVariants::All.generate( &help_generator, dictionary ); + } + else + { + for help in help_variants.iter().sorted() + { + help.generate( &help_generator, dictionary ); + } + } + } + } + + impl< Definition > CommandsAggregatorFormer< Definition > where - End : former::FormingEnd< CommandsAggregator, Context >, + Definition : former::FormerDefinition< Storage = < CommandsAggregator as former::EntityToStorage >::Storage >, { /// Creates a command in the command chain. /// /// # Arguments /// /// * `name` - The name of the command. - pub fn command< IntoName >( self, name : IntoName ) -> CommandFormer< Self, impl former::FormingEnd< Command, Self > > + pub fn command< IntoName >( self, name : IntoName ) -> CommandAsSubformer< Self, impl CommandAsSubformerEnd< Self > > where IntoName : Into< String >, { - let on_end = | command : Command, super_former : Option< Self > | -> Self + let on_end = | command : CommandFormerStorage, super_former : Option< Self > | -> Self { let mut super_former = super_former.unwrap(); let mut dictionary = super_former.storage.dictionary.unwrap_or_default(); - dictionary.register( command ); + dictionary.register( command.preform() ); super_former.storage.dictionary = Some( dictionary ); @@ -156,35 +176,28 @@ pub( crate ) mod private impl CommandsAggregatorFormer { - // qqq : delete on completion - // /// Setter for grammar - // /// - // /// Gets list of available commands - // pub fn grammar< V >( mut self, commands : V ) -> Self - // where - // V : Into< Vec< Command > > - // { - // let verifier = Verifier::former() - // .commands( commands ) - // .form(); - // self.storage.verifier = Some( verifier ); - // self - // } + /// Adds a context to the executor. + /// + /// # Arguments + /// + /// * `value` - The value to be used as the context. + /// + /// # Returns + /// + /// The modified instance of `Self`. + // `'static` means that the value must be owned or live at least as a `Context' + pub fn with_context< T >( mut self, value : T ) -> Self + where + T : Sync + Send + 'static, + { + let mut executor = self.storage.executor.unwrap_or_else( || Executor::former().form() ); + + executor.context = Context::new( value ); + + self.storage.executor = Some( executor ); - // /// Setter for executor - // /// - // /// Gets dictionary of routines( command name -> callback ) - // pub fn executor< H >( mut self, routines : H ) -> Self - // where - // H : Into< HashMap< String, Routine > > - // { - // let executor = ExecutorConverter::former() - // .routines( routines ) - // .form(); - // - // self.storage.executor_converter = Some( executor ); - // self - // } + self + } /// Setter for help content generator /// @@ -207,7 +220,8 @@ pub( crate ) mod private self.storage.help_generator = Some( HelpGeneratorFn::new( func ) ); self } - // qqq : it is good access method, but formed structure should not have help_generator anymore + // aaa : it is good access method, but formed structure should not have help_generator anymore + // aaa : mutator used /// Set callback function that will be executed after validation state /// @@ -235,29 +249,6 @@ pub( crate ) mod private impl CommandsAggregator { - /// Construct CommandsAggregator - fn build( self ) -> CommandsAggregator - { - let mut ca = self; - - let help_generator = std::mem::take( &mut ca.help_generator ).unwrap_or_default(); - let help_variants = std::mem::take( &mut ca.help_variants ); - - if help_variants.contains( &HelpVariants::All ) - { - HelpVariants::All.generate( &help_generator, &mut ca.dictionary ); - } - else - { - for help in help_variants.iter().sorted() - { - help.generate( &help_generator, &mut ca.dictionary ); - } - } - - ca - } - /// Parse, converts and executes a program /// /// Takes a string with program and executes it @@ -267,13 +258,12 @@ pub( crate ) mod private { let Input( ref program ) = program.into_input(); - let raw_program = self.parser.program( program ).map_err( | e | Error::Validation( ValidationError::Parser { input : program.to_string(), error : e } ) )?; + let raw_program = self.parser.parse( program ).map_err( | e | Error::Validation( ValidationError::Parser { input : format!( "{:?}", program ), error : e } ) )?; let grammar_program = self.verifier.to_program( &self.dictionary, raw_program ).map_err( | e | Error::Validation( ValidationError::Verifier( e ) ) )?; - // let exec_program = self.executor_converter.to_program( grammar_program ).map_err( | e | Error::Validation( ValidationError::ExecutorConverter( e ) ) )?; if let Some( callback ) = &self.callback_fn { - callback.0( program, &grammar_program ) + callback.0( &program.join( " " ), &grammar_program ) } self.executor.program( &self.dictionary, grammar_program ).map_err( | e | Error::Execution( e ) ) diff --git a/module/move/wca/src/ca/executor/context.rs b/module/move/wca/src/ca/executor/context.rs index ea83670921..4a338039d6 100644 --- a/module/move/wca/src/ca/executor/context.rs +++ b/module/move/wca/src/ca/executor/context.rs @@ -1,119 +1,89 @@ pub( crate ) mod private { - use std::{ sync::Arc, cell::RefCell }; - use anymap::{ Map, any::CloneAny }; + use std::sync::Arc; /// Container for contexts values /// /// # Examples: /// /// ``` - /// use wca::Context; - /// - /// let ctx = Context::default(); - /// - /// ctx.insert( 42 ); - /// assert_eq!( 42, ctx.get().unwrap() ); - /// ``` - /// - /// ``` - /// # use wca::{ Routine, Context, Value, Args, Props }; + /// # use wca::{ Routine, Handler, Context, Value, Args, Props, VerifiedCommand }; /// # use std::sync::{ Arc, Mutex }; - /// let routine = Routine::new_with_ctx + /// let routine = Routine::from( Handler::from /// ( - /// | ( args, props ), ctx | + /// | ctx : Context, o : VerifiedCommand | /// { - /// let first_arg : i32 = args.get_owned( 0 ).unwrap_or_default(); - /// let ctx_value : Arc< Mutex< i32 > > = ctx.get_or_default(); + /// let first_arg : i32 = o.args.get_owned( 0 ).unwrap_or_default(); + /// let ctx_value : Arc< Mutex< i32 > > = ctx.get().unwrap(); /// /// *ctx_value.lock().unwrap() += first_arg; - /// - /// Ok( () ) /// } - /// ); - /// let ctx = Context::default(); + /// ) ); + /// let ctx = Context::new( Mutex::new( 0 ) ); /// if let Routine::WithContext( callback ) = routine /// { - /// callback( ( Args( vec![ Value::Number( 1.0 ) ] ), Props( Default::default() ) ), ctx.clone() ).unwrap(); + /// let w_command = VerifiedCommand + /// { + /// phrase : "command".into(), + /// internal_command : false, + /// args : Args( vec![ Value::Number( 1.0 ) ] ), + /// props : Props( Default::default() ), + /// }; + /// callback( ctx.clone(), w_command ).unwrap(); /// } - /// assert_eq!( 1, *ctx.get::< Arc< Mutex< i32 > > >().unwrap().lock().unwrap() ); + /// assert_eq!( 1, *ctx.get::< Mutex< i32 > >().unwrap().lock().unwrap() ); /// ``` - // CloneAny needs to deep clone of Context // qqq : ? - #[ derive( Debug, Clone, former::Former ) ] + #[ derive( Debug, Clone ) ] pub struct Context { - inner : Arc< RefCell< Map::< dyn CloneAny > > > - } - - impl ContextFormer - { - /// Initialize Context with some value - pub fn with< T : CloneAny >( mut self, value : T ) -> Self - { - let inner = self.storage.inner.unwrap_or_else( || Context::default().inner ); - inner.borrow_mut().insert( value ); - - self.storage.inner = Some( inner ); - self - } + inner : Arc< dyn std::any::Any + Send + Sync >, } - + impl Default for Context { fn default() -> Self { - Self { inner : Arc::new( RefCell::new( Map::< dyn CloneAny >::new() ) ) } + Self::new( () ) } } - + impl Context { - /// Insert the T value to the context. If it is already exists - replace it - pub fn insert< T : CloneAny >( &self, value : T ) - { - self.inner.borrow_mut().insert( value ); - } - - /// Removes the T value from the context - pub fn remove< T : CloneAny >( &mut self ) -> Option< T > - { - self.inner.borrow_mut().remove::< T >() - } - - // aaa : Bohdan : why unsafe? - // aaa : re-worked. - - /// Return immutable reference on interior object. - pub fn get< T : CloneAny + Clone >( &self ) -> Option< T > - { - self.inner.borrow().get().cloned() - } - - /// Insert the value if it doesn't exists, or take an existing value and return mutable reference to it - pub fn get_or_insert< T : CloneAny + Clone >( &self, value : T ) -> T + /// Creates a new `Context` object with the given value. + /// + /// # Arguments + /// + /// * `value` - The value to be stored in the `Context`. The value must implement the `Send` and `Sync` traits. + /// ``` + // `'static` means that the object must be owned or live at least as a `Context' + pub fn new< T : Send + Sync + 'static >( value : T ) -> Self { - if let Some( value ) = self.get() - { - value - } - else - { - self.insert( value ); - self.get().unwrap() - } + Self { inner : Arc::new( value ) } } + } - /// Insert default value if it doesn't exists, or take an existing value and return mutable reference to it - pub fn get_or_default< T : CloneAny + Default + Clone >( &self ) -> T + impl Context + { + /// This method retrieves a shared reference to an object of type `T` from the context. + /// + /// # Arguments + /// + /// * `&self` - The context object. + /// + /// # Type Parameters + /// + /// * `T` - The type of the object to retrieve. + /// + /// # Returns + /// + /// An `Option` containing a reference-counted smart pointer (`Arc`) to the object of type `T` if it exists in the context. + /// `None` is returned if the object does not exist or if it cannot be downcasted to type `T`. + // `'static` means that the object must be owned or live at least as a `Context' + pub fn get< T : Send + Sync + 'static >( &self ) -> Option< Arc< T > > { - self.get_or_insert( T::default() ) + self.inner.clone().downcast::< T >().ok() } - - // aaa : for Bohdan : why is it deep? how is it deep? - // aaa : how is it useful? Is it? Examples? - // - // aaa : removed } } diff --git a/module/move/wca/src/ca/executor/executor.rs b/module/move/wca/src/ca/executor/executor.rs index f9c977261e..1ba7ce66a4 100644 --- a/module/move/wca/src/ca/executor/executor.rs +++ b/module/move/wca/src/ca/executor/executor.rs @@ -16,7 +16,7 @@ pub( crate ) mod private pub struct Executor { /// The default context for the executor - #[ default( Context::default() ) ] + #[ former( default = Context::default() ) ] pub context : Context, } @@ -79,8 +79,8 @@ pub( crate ) mod private { match routine { - Routine::WithoutContext( routine ) => routine(( Args( command.subjects ), Props( command.properties ) )), - Routine::WithContext( routine ) => routine( ( Args( command.subjects ), Props( command.properties ) ), ctx ), + Routine::WithoutContext( routine ) => routine( command ), + Routine::WithContext( routine ) => routine( ctx, command ), } } diff --git a/module/move/wca/src/ca/executor/routine.rs b/module/move/wca/src/ca/executor/routine.rs index 6f9927bb90..146a290639 100644 --- a/module/move/wca/src/ca/executor/routine.rs +++ b/module/move/wca/src/ca/executor/routine.rs @@ -28,18 +28,16 @@ pub( crate ) mod private /// /// ## Use case /// ``` - /// # use wca::{ Routine, Args }; - /// let routine = Routine::new + /// # use wca::{ Routine, Handler, VerifiedCommand }; + /// let routine = Routine::from( Handler::from /// ( - /// |( args, _ ) : ( Args, _ )| + /// | o : VerifiedCommand | /// { - /// let first_arg : i32 = args.get_owned( 0 ).unwrap(); - /// - /// Ok( () ) + /// let first_arg : i32 = o.args.get_owned( 0 ).unwrap(); /// } - /// ); + /// ) ); /// ``` - #[ derive( Debug ) ] + #[ derive( Debug, Clone ) ] pub struct Args( pub Vec< Value > ); impl Args @@ -89,18 +87,16 @@ pub( crate ) mod private /// /// ## Use case /// ``` - /// # use wca::{ Routine, Props }; - /// let routine = Routine::new + /// # use wca::{ Routine, Handler, Props, VerifiedCommand }; + /// let routine = Routine::from( Handler::from /// ( - /// |( _, props ) : ( _, Props )| + /// | o : VerifiedCommand | /// { - /// let key_option : i32 = props.get_owned( "key" ).unwrap(); - /// - /// Ok( () ) + /// let key_option : i32 = o.props.get_owned( "key" ).unwrap(); /// } - /// ); + /// ) ); /// ``` - #[ derive( Debug ) ] + #[ derive( Debug, Clone ) ] pub struct Props( pub HashMap< String, Value > ); impl Props @@ -133,36 +129,44 @@ pub( crate ) mod private // qqq : make 0-arguments, 1-argument, 2-arguments, 3 arguments versions // aaa : done. now it works with the following variants: // fn(), fn(args), fn(props), fn(args, props), fn(context), fn(context, args), fn(context, props), fn(context, args, props) - type RoutineWithoutContextFn = dyn Fn( ( Args, Props ) ) -> Result< () >; - type RoutineWithContextFn = dyn Fn( ( Args, Props ), Context ) -> Result< () >; + + type RoutineWithoutContextFn = dyn Fn( VerifiedCommand ) -> Result< () >; + type RoutineWithContextFn = dyn Fn( Context, VerifiedCommand ) -> Result< () >; /// /// Routine handle. - /// + /// /// ``` - /// # use wca::Routine; - /// let routine = Routine::new + /// # use wca::{ Handler, Routine }; + /// let routine = Routine::from( Handler::from /// ( - /// |( args, props )| + /// || /// { /// // Do what you need to do - /// - /// Ok( () ) /// } - /// ); + /// ) ); /// ``` /// /// ``` - /// # use wca::Routine; - /// let routine = Routine::new_with_ctx + /// # use wca::{ Handler, Routine, VerifiedCommand }; + /// let routine = Routine::from( Handler::from /// ( - /// | ( args, props ), ctx | + /// | o : VerifiedCommand | /// { /// // Do what you need to do + /// } + /// ) ); + /// ``` /// - /// Ok( () ) + /// ``` + /// # use wca::{ Handler, Routine }; + /// let routine = Routine::from( Handler::from + /// ( + /// | ctx, o | + /// { + /// // Do what you need to do /// } - /// ); + /// ) ); pub struct Handler< I, O >( Box< dyn Fn( I ) -> O > ); @@ -186,21 +190,10 @@ pub( crate ) mod private } } - impl< F, R > From< F > for Handler< Args, R > - where - R : IntoResult + 'static, - F : Fn( Args ) -> R + 'static, - { - fn from( value : F ) -> Self - { - Self( Box::new( value ) ) - } - } - - impl< F, R > From< F > for Handler< Props, R > + impl< F, R > From< F > for Handler< VerifiedCommand, R > where R : IntoResult + 'static, - F : Fn( Props ) -> R + 'static, + F : Fn( VerifiedCommand ) -> R + 'static, { fn from( value : F ) -> Self { @@ -208,17 +201,6 @@ pub( crate ) mod private } } - impl< F, R > From< F > for Handler< ( Args, Props ), R > - where - R : IntoResult + 'static, - F : Fn( Args, Props ) -> R + 'static, - { - fn from( value : F ) -> Self - { - Self( Box::new( move |( a, p )| value( a, p ) ) ) - } - } - // with context impl< F, R > From< F > for Handler< Context, R > where @@ -231,21 +213,10 @@ pub( crate ) mod private } } - impl< F, R > From< F > for Handler< ( Context, Args ), R > - where - R : IntoResult + 'static, - F : Fn( Context, Args ) -> R + 'static, - { - fn from( value : F ) -> Self - { - Self( Box::new( move |( ctx, a )| value( ctx, a ) ) ) - } - } - - impl< F, R > From< F > for Handler< ( Context, Props ), R > + impl< F, R > From< F > for Handler< ( Context, VerifiedCommand ), R > where R : IntoResult + 'static, - F : Fn( Context, Props ) -> R + 'static, + F : Fn( Context, VerifiedCommand ) -> R + 'static, { fn from( value : F ) -> Self { @@ -253,17 +224,6 @@ pub( crate ) mod private } } - impl< F, R > From< F > for Handler< ( Context, Args, Props ), R > - where - R : IntoResult + 'static, - F : Fn( Context, Args, Props ) -> R + 'static, - { - fn from( value : F ) -> Self - { - Self( Box::new( move |( c, a, p )| value( c, a, p ) ) ) - } - } - impl< I, O > From< Handler< I, O > > for Routine where I : 'static, @@ -311,28 +271,12 @@ pub( crate ) mod private Self::WithoutContext( Rc::new( move | _ | { value( () )?; Ok( () ) } ) ) } } - - impl From< Box< dyn Fn( Args ) -> Result< () > > > for Routine - { - fn from( value : Box< dyn Fn( Args ) -> Result< () > > ) -> Self - { - Self::WithoutContext( Rc::new( move |( a, _ )| { value( a )?; Ok( () ) } ) ) - } - } - - impl From< Box< dyn Fn( Props ) -> Result< () > > > for Routine - { - fn from( value : Box< dyn Fn( Props ) -> Result< () > > ) -> Self - { - Self::WithoutContext( Rc::new( move |( _, p )| { value( p )?; Ok( () ) } ) ) - } - } - - impl From< Box< dyn Fn(( Args, Props )) -> Result< () > > > for Routine + + impl From< Box< dyn Fn( VerifiedCommand ) -> Result< () > > > for Routine { - fn from( value : Box< dyn Fn(( Args, Props )) -> Result< () > > ) -> Self + fn from( value : Box< dyn Fn( VerifiedCommand ) -> Result< () > > ) -> Self { - Self::WithoutContext( Rc::new( move |( a, p )| { value(( a, p ))?; Ok( () ) } ) ) + Self::WithoutContext( Rc::new( move | a | { value( a )?; Ok( () ) } ) ) } } @@ -341,87 +285,21 @@ pub( crate ) mod private { fn from( value : Box< dyn Fn( Context ) -> Result< () > > ) -> Self { - Self::WithContext( Rc::new( move | _, ctx | { value( ctx )?; Ok( () ) } ) ) + Self::WithContext( Rc::new( move | ctx, _ | { value( ctx )?; Ok( () ) } ) ) } } - impl From< Box< dyn Fn(( Context, Args )) -> Result< () > > > for Routine + impl From< Box< dyn Fn(( Context, VerifiedCommand )) -> Result< () > > > for Routine { - fn from( value : Box< dyn Fn(( Context, Args )) -> Result< () > > ) -> Self + fn from( value : Box< dyn Fn(( Context, VerifiedCommand )) -> Result< () > > ) -> Self { - Self::WithContext( Rc::new( move | ( a, _ ), ctx | { value(( ctx, a ))?; Ok( () ) } ) ) + Self::WithContext( Rc::new( move | ctx, a | { value(( ctx, a ))?; Ok( () ) } ) ) } } - impl From< Box< dyn Fn(( Context, Props )) -> Result< () > > > for Routine - { - fn from( value : Box< dyn Fn(( Context, Props )) -> Result< () > > ) -> Self - { - Self::WithContext( Rc::new( move | ( _, p ), ctx | { value(( ctx, p ))?; Ok( () ) } ) ) - } - } - - impl From< Box< dyn Fn(( Context, Args, Props )) -> Result< () > > > for Routine - { - fn from( value : Box< dyn Fn(( Context, Args, Props )) -> Result< () > > ) -> Self - { - Self::WithContext( Rc::new( move | ( a, p ), ctx | { value(( ctx, a, p ))?; Ok( () ) } ) ) - } - } - - // qqq : why Rc is necessary? why not just box? // aaa : to be able to clone Routines - impl Routine - { - /// - /// Create new routine. - /// - /// ``` - /// # use wca::Routine; - /// let routine = Routine::new - /// ( - /// |( args, props )| - /// { - /// // Do what you need to do - /// - /// Ok( () ) - /// } - /// ); - /// ``` - - pub fn new< F >( callback : F ) -> Self - where - F : Fn(( Args, Props )) -> Result< () > + 'static, - { - Routine::WithoutContext( Rc::new( callback ) ) - } - - /// - /// Create new routine with context. - /// - /// ``` - /// # use wca::Routine; - /// let routine = Routine::new_with_ctx - /// ( - /// | ( args, props ), ctx | - /// { - /// // Do what you need to do - /// - /// Ok( () ) - /// } - /// ); - /// ``` - - pub fn new_with_ctx< F >( callback : F ) -> Self - where - F : Fn( ( Args, Props ), Context ) -> Result< () > + 'static, - { - Routine::WithContext( Rc::new( callback ) ) - } - } - impl PartialEq for Routine { fn eq( &self, other : &Self ) -> bool diff --git a/module/move/wca/src/ca/grammar/command.rs b/module/move/wca/src/ca/grammar/command.rs index 11cba64dc1..2f12e03921 100644 --- a/module/move/wca/src/ca/grammar/command.rs +++ b/module/move/wca/src/ca/grammar/command.rs @@ -5,7 +5,7 @@ pub( crate ) mod private use { Handler, Routine, Type }; use std::collections::HashMap; - use former::Former; + use former::{ Former, StoragePreform }; /// A description of a Value in a command. Used to specify the expected type and provide a hint for the Value. /// @@ -27,7 +27,7 @@ pub( crate ) mod private /// expected type of a value pub kind : Type, /// subject optional parameter - #[ default( false ) ] + #[ former( default = false ) ] pub optional : bool, } @@ -42,16 +42,16 @@ pub( crate ) mod private /// expected type of a value kind : Type, /// subject optional parameter - #[ default( false ) ] + #[ former( default = false ) ] optional : bool, - #[ setter( false ) ] - #[ default( Vec::new() ) ] + #[ scalar( setter = false ) ] + #[ former( default = Vec::new() ) ] properties_aliases : Vec< String >, } - impl< C, End > PropertyDescriptionFormer< C, End > + impl< Definition > PropertyDescriptionFormer< Definition > where - End : former::FormingEnd< PropertyDescription, C >, + Definition : former::FormerDefinition< Storage = < PropertyDescription as former::EntityToStorage >::Storage >, { pub fn alias< IntoName >( mut self, name : IntoName ) -> Self where @@ -86,17 +86,17 @@ pub( crate ) mod private #[ derive( Debug, Clone, PartialEq, Eq ) ] #[ derive( Former ) ] + // #[ debug ] pub struct Command { /// Command common hint. - #[ alias( h ) ] pub hint : String, /// Command full hint. - #[ alias( lh ) ] pub long_hint : String, /// Phrase descriptor for command. pub phrase : String, /// Command subjects hints and types. + #[ subform( setter = true ) ] pub subjects : Vec< ValueDescription >, /// Hints and types for command options. pub properties : HashMap< String, ValueDescription >, @@ -107,14 +107,14 @@ pub( crate ) mod private // aaa : here it is // qqq : make it usable and remove default(?) /// The type `Routine` represents the specific implementation of the routine. - #[ setter( false ) ] - #[ default( Routine::new( | _ | { panic!( "No routine available: A handler function for the command is missing" ) } ) ) ] + #[ scalar( setter = false ) ] + #[ former( default = Routine::from( Handler::from( || { panic!( "No routine available: A handler function for the command is missing" ) } ) ) ) ] pub routine : Routine, } - impl< Context, End > CommandFormer< Context, End > + impl< Definition > CommandFormer< Definition > where - End : former::FormingEnd< Command, Context >, + Definition : former::FormerDefinition< Storage = < Command as former::EntityToStorage >::Storage >, { /// Setter for separate properties aliases. pub fn property_alias< S : Into< String > >( mut self, key : S, alias : S ) -> Self @@ -169,27 +169,17 @@ pub( crate ) mod private } } - impl< Context, End > CommandFormer< Context, End > + impl< Definition > CommandFormer< Definition > where - End : former::FormingEnd< Command, Context >, + Definition : former::FormerDefinition< Storage = < Command as former::EntityToStorage >::Storage >, { /// Implements the `subject` method for a value. /// /// This method allows chaining, where `subject` is the current value and `ValueDescription` is the super-former. /// It returns a `ValueDescriptionFormer` which can be used to further build the super-former. - pub fn subject( self ) -> ValueDescriptionFormer< Self, impl former::FormingEnd< ValueDescription, Self > > + pub fn subject( self ) -> ValueDescriptionAsSubformer< Self, impl ValueDescriptionAsSubformerEnd< Self > > { - let on_end = | subject : ValueDescription, super_former : Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - let mut subjects = super_former.storage.subjects.unwrap_or_default(); - subjects.push( subject ); - - super_former.storage.subjects = Some( subjects ); - - super_former - }; - ValueDescriptionFormer::begin( None, Some( self ), on_end ) + self._subjects_add() } /// Sets the name and other properties of the current property. @@ -201,14 +191,15 @@ pub( crate ) mod private /// # Arguments /// /// * `name` - The name of the property. It should implement the `Into< String >` trait. - pub fn property< IntoName >( self, name : IntoName ) -> PropertyDescriptionFormer< Self, impl former::FormingEnd< PropertyDescription, Self > > + pub fn property< IntoName >( self, name : IntoName ) -> PropertyDescriptionAsSubformer< Self, impl PropertyDescriptionAsSubformerEnd< Self > > where IntoName : Into< String >, { - let on_end = | property : PropertyDescription, super_former : Option< Self > | -> Self + let on_end = | property : PropertyDescriptionFormerStorage, super_former : Option< Self > | -> Self { let mut super_former = super_former.unwrap(); let mut properties = super_former.storage.properties.unwrap_or_default(); + let property = property.preform(); let value = ValueDescription { hint : property.hint, diff --git a/module/move/wca/src/ca/grammar/dictionary.rs b/module/move/wca/src/ca/grammar/dictionary.rs index 21fc962efb..a9a79d198a 100644 --- a/module/move/wca/src/ca/grammar/dictionary.rs +++ b/module/move/wca/src/ca/grammar/dictionary.rs @@ -20,7 +20,7 @@ pub( crate ) mod private #[ derive( Debug, Default, Former, Clone ) ] pub struct Dictionary { - #[ setter( false ) ] + #[ scalar( setter = false, hint = false ) ] pub( crate ) commands : HashMap< String, Command >, } diff --git a/module/move/wca/src/ca/grammar/types.rs b/module/move/wca/src/ca/grammar/types.rs index 16126e0f03..1ee53a654e 100644 --- a/module/move/wca/src/ca/grammar/types.rs +++ b/module/move/wca/src/ca/grammar/types.rs @@ -58,24 +58,25 @@ pub( crate ) mod private /// # Example: /// /// ``` - /// # use wca::{ VerifiedCommand, Value }; + /// # use wca::{ VerifiedCommand, Value, Args, Props }; /// # use std::collections::HashMap; /// let command = VerifiedCommand /// { /// phrase : "command".to_string(), + /// internal_command : false, /// // Here is numeric value used - /// subjects : vec![ Value::Number( 3.14 ) ], - /// properties : HashMap::from_iter( + /// args : Args( vec![ Value::Number( 3.14 ) ] ), + /// props : Props( HashMap::from_iter( /// [ /// // Here is string value used /// ( "string_prop".to_string(), Value::String( "value".to_string() ) ), - /// ]) + /// ])) /// }; /// - /// let number : f32 = command.subjects[ 0 ].clone().into(); + /// let number : f32 = command.args.get_owned( 0 ).unwrap(); /// assert_eq!( 3.14, number ); /// - /// let number : i32 = command.subjects[ 0 ].clone().into(); + /// let number : i32 = command.args.get_owned( 0 ).unwrap(); /// assert_eq!( 3, number ); /// ``` #[ derive( Debug, Clone, PartialEq ) ] diff --git a/module/move/wca/src/ca/help.rs b/module/move/wca/src/ca/help.rs index f4b7f6c3ef..028a79347e 100644 --- a/module/move/wca/src/ca/help.rs +++ b/module/move/wca/src/ca/help.rs @@ -29,7 +29,7 @@ pub( crate ) mod private pub struct HelpGeneratorOptions< 'a > { /// Prefix that will be shown before command name - #[ default( String::new() ) ] + #[ former( default = String::new() ) ] pub command_prefix : String, /// Show help for the specified commands pub for_commands : Vec< &'a Command >, @@ -184,16 +184,16 @@ pub( crate ) mod private let generator = helper.clone(); let moved_phrase = phrase.clone(); - let routine = move | args : Args, props : Props | + let routine = move | o : VerifiedCommand | { let subject_help = grammar.command( &moved_phrase ); match &subject_help { Some( Command { routine: Routine::WithoutContext( help ), .. } ) - if !args.is_empty() => help(( args, props ))?, + if !o.args.0.is_empty() => help( o )?, _ => { - let format_prop : String = props.get_owned( "format" ).unwrap_or_default(); + let format_prop : String = o.props.get_owned( "format" ).unwrap_or_default(); let format = match format_prop.as_str() { "md" | "markdown" => HelpFormat::Markdown, @@ -248,16 +248,16 @@ pub( crate ) mod private let generator = helper.clone(); let moved_phrase = phrase.clone(); - let routine = move | args : Args, props : Props | + let routine = move | o : VerifiedCommand | { let full_help = grammar.command( &moved_phrase ); match &full_help { Some( Command { routine: Routine::WithoutContext( help ), .. } ) - if args.is_empty() => help(( args, props ))?, + if o.args.0.is_empty() => help( o )?, _ => { - let command = args.get_owned::< String >( 0 ).unwrap(); + let command = o.args.get_owned::< String >( 0 ).unwrap(); let cmd = grammar.commands.get( &command ).ok_or_else( || anyhow!( "Can not found help for command `{command}`" ) )?; let args = HelpGeneratorOptions::former() @@ -361,7 +361,7 @@ pub( crate ) mod private /// help_fn.exec( grammar, HelpGeneratorOptions::former().form() ); /// // or /// # let cmd = Command::former().form(); - /// help_fn.exec( grammar, HelpGeneratorOptions::former().for_command( &cmd ).form() ); + /// help_fn.exec( grammar, HelpGeneratorOptions::former().for_commands( [ &cmd ] ).form() ); /// ``` #[ derive( Clone ) ] pub struct HelpGeneratorFn( HelpFunctionFn ); diff --git a/module/move/wca/src/ca/input.rs b/module/move/wca/src/ca/input.rs index 4deac4264c..c8ef39a28b 100644 --- a/module/move/wca/src/ca/input.rs +++ b/module/move/wca/src/ca/input.rs @@ -15,11 +15,11 @@ pub( crate ) mod private /// A structure representing an input with a single string value. /// - /// This struct is designed to encapsulate a single piece of input data as a `String`. + /// This struct is designed to encapsulate a single piece of input data as a `Vec< String >`. /// It provides a simple wrapper that can be used to convert various types of string /// representations into a uniform `Input` struct. #[ derive( Debug ) ] - pub struct Input( pub String ); + pub struct Input( pub Vec< String > ); /// A trait for converting various types into `Input`. /// @@ -51,7 +51,7 @@ pub( crate ) mod private fn into_input( self ) -> Input { - Input( self.to_string() ) + Input( self.split( ' ' ).map( ToString::to_string ).collect() ) } } @@ -59,7 +59,7 @@ pub( crate ) mod private { fn into_input( self ) -> Input { - Input( self ) + Input( self.split( ' ' ).map( ToString::to_string ).collect() ) } } @@ -67,7 +67,7 @@ pub( crate ) mod private { fn into_input( self ) -> Input { - Input( self.join( " " ) ) + Input( self ) } } diff --git a/module/move/wca/src/ca/parser/command.rs b/module/move/wca/src/ca/parser/command.rs index 13a9be8230..1d39e9bf34 100644 --- a/module/move/wca/src/ca/parser/command.rs +++ b/module/move/wca/src/ca/parser/command.rs @@ -1,238 +1,54 @@ pub( crate ) mod private { - use crate:: - { - ca:: - { - Parser, - ParsedCommand as Command, - parser::parser::any_word, - }, - wtools - }; - use wtools::{ error:: Result, err }; - use nom:: - { - branch::alt, - bytes::complete::{ tag, take_while, escaped }, - character::complete::{ alpha1, multispace0, multispace1, none_of, one_of }, - combinator::{ map, map_opt, recognize, eof }, - multi::many0, - sequence::{ tuple, pair, delimited }, - IResult, - }; - - /// Can parse Commands - pub trait CommandParser - { - /// Parses first command from string - /// - /// Command name must starts with letter or underscore - fn command( &self, input : &str ) -> Result< Command >; - } - - type CommandParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, Command > + 'a >; - - /// Can be used as function to parse a Command - pub trait CommandParserFn : GetCommandPrefix + CommandNameParserFn + CommandSubjectParserFn + CommandPropertyParserFn - { - /// Returns function that can parse a Command - fn command_fn( &self ) -> CommandParserFunction< '_ > - { - let command_prefix = self.get_command_prefix(); - Box::new( move | input : &str | - map - ( - tuple - (( - multispace0, - tag( &*format!( "{command_prefix}" ) ), - alt - (( - // it is a command - map( tuple - (( - Self::command_name_fn(), - many0( tuple(( multispace1, self.command_subject_fn() )) ), - //? why multispace0 - many0( tuple(( multispace0, self.command_property_fn() )) ) - )), |( name, subjects, props )| - { - Command - { - name : name.to_owned(), - subjects : subjects.into_iter().filter_map( |( _, subject )| if subject.is_empty() { None } else { Some( subject ) } ).collect(), - properties : props.into_iter().map( |( _, prop )| prop ).collect() - } - }), - // spacial cases - map( tag( "?" ), | _ | Command { name : format!( "{}?", command_prefix ), ..Default::default() } ), - map - ( - eof, - | _ | - Command - { - name : command_prefix.to_string(), - ..Default::default() - } - ) - )), - )), - |( _, _, command )| command - )( input ) ) - } - } - - pub trait GetCommandPrefix - { - fn get_command_prefix( &self ) -> char; - } - - impl GetCommandPrefix for Parser - { - fn get_command_prefix( &self ) -> char { self.command_prefix } - } - - type CommandNameParserFunction = Box< dyn Fn( &str ) -> IResult< &str, &str > >; - - /// Can be used as function to parse a Command name - pub trait CommandNameParserFn - { - /// Returns function that can parse a Command name - fn command_name_fn() -> CommandNameParserFunction; - } - - type CommandSubjectParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, String > + 'a >; - - /// Can be used as function to parse a Command subject - pub trait CommandSubjectParserFn - { - /// Returns function that can parse a Command subject - fn command_subject_fn( &self ) -> CommandSubjectParserFunction< '_ >; - } - - type CommandPropertyParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, ( String, String ) > + 'a >; - - /// Can be used as function to parse a Command property - pub trait CommandPropertyParserFn - { - /// Returns function that can parse a Command property - fn command_property_fn( &self ) -> CommandPropertyParserFunction< '_ >; - } - - impl CommandNameParserFn for Parser - { - fn command_name_fn() -> CommandNameParserFunction - { - Box::new - ( - move | input : &str | - recognize( pair - ( - alt(( alpha1, tag( "_" ) )), - any_word - ))( input ) - ) - } - } - - impl CommandSubjectParserFn for Parser - { - fn command_subject_fn( &self ) -> CommandSubjectParserFunction< '_ > - { - // ? looks not good - // reason - all words can be `subject` - let prop_delimeter = self.prop_delimeter; - let namespace_delimeter = self.namespace_delimeter.clone(); - - Box::new - ( - move | input : &str | - { - alt - (( - // quoted subject - map( delimited( tag( "\"" ), escaped( none_of( "\\\"" ), '\\', one_of( "\\\"" ) ), tag( "\"" ) ), | s : &str | s.replace( "\\\"", "\"" ).replace( "\\\\", "\\" ) ), - map( delimited( tag( "'" ), escaped( none_of( "\\'" ), '\\', one_of( "\\'" ) ), tag( "'" ) ), | s : &str | s.replace( "\\\'", "'" ).replace( "\\\\", "\\" ) ), - // single word subject - map_opt - ( - any_word, - | word | - { - // if next word - is a command => it isn't subject - // if you want pass command as subject - take it in quotes - let not_a_command = self.command_fn()( word ).is_err(); - if not_a_command && !word.contains( prop_delimeter ) && !word.contains( &*namespace_delimeter ) - { - Some( word.to_owned() ) - } - else - { - None - } - } - ) - ))( input ) - } - ) - } - } - - impl Parser - { - fn propery_name( &self ) -> impl Fn( &str ) -> IResult< &str, &str > - { - let property_delimeter = self.prop_delimeter; - move | input : &str | - recognize( pair - ( - alt(( alpha1, tag( "_" ) )), - take_while( | c : char | !c.is_whitespace() && c != property_delimeter ) - ))( input ) - } - } - - impl CommandPropertyParserFn for Parser - { - fn command_property_fn( &self ) -> CommandPropertyParserFunction< '_ > - { - let property_delimeter = self.prop_delimeter; - Box::new - ( - move | input : &str | - map - ( - tuple - (( - self.propery_name(), - tag( &*format!( "{property_delimeter}" ) ), - alt - (( - // quoted value - map( delimited( tag( "\"" ), escaped( none_of( "\\\"" ), '\\', one_of( "\\\"" ) ), tag( "\"" ) ), | s : &str | s.replace( "\\\"", "\"" ).replace( "\\\\", "\\" ) ), - map( delimited( tag( "'" ), escaped( none_of( "\\'" ), '\\', one_of( "\\'" ) ), tag( "'" ) ), | s : &str | s.replace( "\\\'", "'" ).replace( "\\\\", "\\" ) ), - // single word - map( any_word, | s : &str | s.to_owned() ), - )) - )), - |( name, _, value ) : ( &str, _, _ ) | ( name.to_owned(), value ) - )( input ) - ) - } - } - - impl CommandParserFn for Parser {} - - impl CommandParser for Parser - { - fn command< 'a >( &'a self, input : &'a str ) -> Result< Command > - { - self.command_fn()( input ) - .map( |( _, command )| command ) - .map_err( | _ | err!( "Fail to parse `Command`" ) ) - } + use std::collections::HashMap; + + /// Represents a program that contains one or more namespaces, where each namespace contains a list of commands. + /// + /// A `Program` consists of one or more commannd + /// + /// The program can be executed by iterating over each commands and executing it + // qqq : xxx : for Bohdan : Commands should be here instead of Namespace + // qqq : remove concept Namespace + // qqq : introduce concept Dictionary for grammar + #[ derive( Debug, Clone, PartialEq, Eq ) ] + pub struct Program< Command > + { + /// list of namespaces with commands + pub commands : Vec< Command >, + } + + /// Represents a parsed command that has been extracted from an input string by a `Parser`. + /// + /// The `ParsedCommand` struct is designed to be flexible and allow for a wide variety of commands to be parsed and represented. However, this flexibility also means that a `ParsedCommand` may contain invalid or unexpected data. + /// + /// # Example: + /// + /// ``` + /// # use wca::ParsedCommand; + /// # use std::collections::HashMap; + /// ParsedCommand + /// { + /// name : "command".to_string(), + /// subjects : vec![ "subject_value".to_string(), /* ... */ ], + /// properties : HashMap::from_iter( + /// [ + /// ( "prop_name".to_string(), "raw_prop_value".to_string() ), + /// /* ... */ + /// ]) + /// }; + /// ``` + /// + /// In the above example, a `ParsedCommand` instance is created with the name "command", a single subject "subject_value", and one property "prop_name" with a raw value of "raw_prop_value". + /// + #[ derive( Default, Debug, Clone, PartialEq, Eq ) ] + pub struct ParsedCommand + { + /// name of command without delimiter + pub name : String, + /// list of all subjects for the command + pub subjects : Vec< String >, + /// dictionary of properties. Each property has a name and a raw value + pub properties : HashMap< String, String > } } @@ -240,8 +56,6 @@ pub( crate ) mod private crate::mod_interface! { - exposed use CommandParser; - protected use CommandParserFn; + exposed use Program; + exposed use ParsedCommand; } - -// qqq : use orphan instead of exposed for ALL files in the folder, dont use prelude for structs \ No newline at end of file diff --git a/module/move/wca/src/ca/parser/entities.rs b/module/move/wca/src/ca/parser/entities.rs deleted file mode 100644 index 03a9b1dc28..0000000000 --- a/module/move/wca/src/ca/parser/entities.rs +++ /dev/null @@ -1,61 +0,0 @@ -pub( crate ) mod private -{ - use std::collections::HashMap; - - /// Represents a program that contains one or more namespaces, where each namespace contains a list of commands. - /// - /// A `Program` consists of one or more commannd - /// - /// The program can be executed by iterating over each commands and executing it - // qqq : xxx : for Bohdan : Commands should be here instead of Namespace - // qqq : remove concept Namespace - // qqq : introduce concept Dictionary for grammar - #[ derive( Debug, Clone, PartialEq, Eq ) ] - pub struct Program< Command > - { - /// list of namespaces with commands - pub commands : Vec< Command >, - } - - /// Represents a parsed command that has been extracted from an input string by a `Parser`. - /// - /// The `ParsedCommand` struct is designed to be flexible and allow for a wide variety of commands to be parsed and represented. However, this flexibility also means that a `ParsedCommand` may contain invalid or unexpected data. - /// - /// # Example: - /// - /// ``` - /// # use wca::ParsedCommand; - /// # use std::collections::HashMap; - /// ParsedCommand - /// { - /// name : "command".to_string(), - /// subjects : vec![ "subject_value".to_string(), /* ... */ ], - /// properties : HashMap::from_iter( - /// [ - /// ( "prop_name".to_string(), "raw_prop_value".to_string() ), - /// /* ... */ - /// ]) - /// }; - /// ``` - /// - /// In the above example, a `ParsedCommand` instance is created with the name "command", a single subject "subject_value", and one property "prop_name" with a raw value of "raw_prop_value". - /// - #[ derive( Default, Debug, Clone, PartialEq, Eq ) ] - pub struct ParsedCommand - { - /// name of command without delimiter - pub name : String, - /// list of all subjects for the command - pub subjects : Vec< String >, - /// dictionary of properties. Each property has a name and a raw value - pub properties : HashMap< String, String > - } -} - -// - -crate::mod_interface! -{ - exposed use Program; - exposed use ParsedCommand; -} diff --git a/module/move/wca/src/ca/parser/mod.rs b/module/move/wca/src/ca/parser/mod.rs index 3360410874..47a8d4c325 100644 --- a/module/move/wca/src/ca/parser/mod.rs +++ b/module/move/wca/src/ca/parser/mod.rs @@ -1,11 +1,8 @@ crate::mod_interface! { - /// Parser configuration - layer parser; - /// Implementation for parsing command + /// Parsed command layer command; - /// Implementation for parsing program - layer program; - /// Entities representation to interact with - layer entities; + + /// Parser. + layer parser; } diff --git a/module/move/wca/src/ca/parser/parser.rs b/module/move/wca/src/ca/parser/parser.rs index 58da2393fe..a952e6427e 100644 --- a/module/move/wca/src/ca/parser/parser.rs +++ b/module/move/wca/src/ca/parser/parser.rs @@ -1,66 +1,160 @@ -pub( crate ) mod private +mod private { - use std::borrow::Cow; - use former::Former; - use nom:: - { - bytes::complete::take_while, - IResult, - }; + use crate::*; - /// `Parser` provides parsing command strings into `ParsedCommand` objects. - /// It allows you to specify the symbols that will be used to interpret the command string, such as the command delimiter, property delimiter, and namespace delimiter. - /// - /// ``` - /// use wca::{ Parser, CommandParser }; - /// # fn main() -> Result< (), Box< dyn std::error::Error > > { - /// /// Configure the parser - /// let parser = Parser::former() - /// .command_prefix( '.' ) - /// .prop_delimeter( ':' ) - /// .form(); - /// - /// /// Parse a command from a` string - /// let raw_command = parser.command( ".command subject_value prop_name:prop_value" )?; - /// # Ok( () ) } - /// ``` - /// - /// In the above example, a `Parser` object is created and configured to accept commands with a `.` prefix and `:` delimiters for properties. - /// - /// Note that `Parser` uses `CommandParser` trait to parse commands from user input( You can also use `NamespaceParser` to parse namespaces, or `ProgramParser` to parse programs from string ). - /// + use std::collections::HashMap; + + use error_tools::{ Result, return_err }; + + /// `Parser` is a struct used for parsing data. #[ derive( Debug ) ] - #[ derive( Former ) ] - pub struct Parser + pub struct Parser; + + impl Parser { - /// Symbol that will be interpreted as the beginning of a command - /// - /// command_delimiter = `.` + /// Parses a vector of command line arguments and returns a `Program` containing the parsed commands. /// - /// ".command" -> Command( "command" ) - #[ default( '.' ) ] - pub command_prefix : char, - /// Symbol that will be interpreted as a separator for the name and value of the property + /// # Arguments /// - /// prop_delimiter = `:` + /// * `args` - A vector of strings representing the command line arguments. /// - /// "prop:value" -> ( "prop", "value" ) - #[ default( ':' ) ] - pub prop_delimeter : char, - /// String that will be interpreted as a separator for namespaces + /// # Returns /// - /// namespace_delimiter = ".also" - /// - /// "< commands1 > .also < commands2 >" -> Namespace( < commands1 > ), Namespace( < commands2 > ) - #[ default( ".also" ) ] - pub namespace_delimeter : Cow< 'static, str >, - } + /// Returns a `Result` with a `Program` containing the parsed commands if successful, or an error if parsing fails. + pub fn parse< As, A >( &self, args : As ) -> Result< Program< ParsedCommand > > + where + As : IntoIterator< Item = A >, + A : Into< String >, + { + let args = args.into_iter().map( Into::into ).collect::< Vec< _ > >(); + let mut commands = vec![]; + let mut i = 0; + while i < args.len() + { + let ( command, relative_pos ) = Self::parse_command( &args[ i.. ] )?; + i += relative_pos; + commands.push( command ); + } - /// Parses first word from string. All characters before first space - pub fn any_word( input : &str ) -> IResult< &str, &str > - { - let s = take_while( | c : char | !c.is_whitespace() )( input ); - s + Ok( Program { commands } ) + } + + // with dot at the beginning + fn valid_command_name( input : &str ) -> bool + { + if let Some( name ) = input.strip_prefix( '.' ) + { + name.is_empty() || name.starts_with( '?' ) || name.chars().next().is_some_and( | c | c.is_alphanumeric() ) + } + else + { + false + } + } + + // returns ParsedCommand and relative position of the last parsed item + fn parse_command( args : &[ String ] ) -> Result< ( ParsedCommand, usize ) > + { + if args.is_empty() { + return_err!( "Unexpected behaviour: Try to parse command without input" ); + } + + let mut i = 0; + + if !Self::valid_command_name( &args[ i ] ) + { + return_err!( "Unexpected input: Expected a command, found: `{}`", args[ i ] ); + } + let name = match args[ i ].strip_prefix( '.' ).unwrap() + { + "" => ".", + "?" => ".?", + other => other, + }; + i += 1; + let ( subjects, properties, relative_pos ) = Self::parse_command_args( &args[ i .. ] )?; + + i += relative_pos; + + return Ok( + ( + ParsedCommand + { + name : name.to_string(), + subjects, + properties, + }, + i, + )) + } + + // returns ( subjects, properties, relative_end_pos ) + fn parse_command_args( args : &[ String ] ) -> Result< ( Vec< String >, HashMap< String, String >, usize ) > + { + let mut i = 0; + + let mut subjects = vec![]; + let mut properties = HashMap::new(); + + let mut properties_turn = false; + while i < args.len() + { + let item = &args[ i ]; + + if Self::valid_command_name( item ) { break; } + + if item.contains( ':' ) + { + properties_turn = true; + let ( name, value ) = item.split_once( ':' ).unwrap(); + // prop:value + if !value.is_empty() + { + properties.insert( name.to_string(), value.to_string() ); + } + // prop: value + else if args.len() > i + 1 + { + properties.insert( name.to_string(), args[ i + 1 ].to_string() ); + i += 1; + } + // we can identify this as a subject, can't we? + // prop: + else + { + return_err!( "Unexpected input '{}': Detected a possible property key preceding the ':' character. However, no corresponding value was found.", item ); + } + } + // prop : value | prop :value + else if args.len() > i + 1 && args[ i + 1 ].starts_with( ':' ) + { + // :value + if args[ i + 1 ].len() > 1 + { + properties.insert( args[ i ].clone(), args[ i + 1 ].strip_prefix( ':' ).unwrap().to_string() ); + i += 1; + } + // : value + else if args.len() > i + 2 + { + properties.insert( args[ i ].clone(), args[ i + 2 ].clone() ); + i += 2; + } + // : + else + { + return_err!( "Unexpected input '{} :': Detected a possible property key preceding the ':' character. However, no corresponding value was found.", item ); + } + } + + else if !properties_turn { subjects.push( item.to_string() ); } + + else { return_err!( "Unexpected input: Expected `command` or `property`, found: `{}`", item ); } + i += 1; + } + + Ok(( subjects, properties, i )) + } } } @@ -69,5 +163,4 @@ pub( crate ) mod private crate::mod_interface! { exposed use Parser; - protected use any_word; } diff --git a/module/move/wca/src/ca/parser/program.rs b/module/move/wca/src/ca/parser/program.rs deleted file mode 100644 index 5ecdf05a71..0000000000 --- a/module/move/wca/src/ca/parser/program.rs +++ /dev/null @@ -1,73 +0,0 @@ -pub( crate ) mod private -{ - use crate::*; - use { - Program, ParsedCommand, - Parser, - ca::parser::command::CommandParserFn, - wtools::{ error::Result, err }, - }; - use nom:: - { - character::complete::anychar, - combinator::{ map, not }, - multi::many_till, - IResult, - }; - - /// Can parser Programs - pub trait ProgramParser - { - /// Parses program from string - fn program( &self, input : &str ) -> Result< Program< ParsedCommand > >; - } - - type ProgramParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, Program< ParsedCommand > > + 'a >; - - /// Can be used as function to parse a Namespace - pub( crate ) trait ProgramParserFn : CommandParserFn - { - /// Returns function that can parse a Namespace - fn program_fn( &self ) -> ProgramParserFunction< '_ > - { - Box::new - ( - move | input : &str | - map( many_till - ( - self.command_fn(), - not( anychar ) - ), |( commands, _ )| Program { commands } - )( input ) - ) - } - } - - impl ProgramParserFn for Parser {} - - impl ProgramParser for Parser - { - fn program< 'a >( &'a self, input : &'a str ) -> Result< Program< ParsedCommand > > - { - self.program_fn()( input.trim() ) - .map( |( _, program )| program ) - .map_err - ( - | e | - { - match e - { - nom::Err::Incomplete( _ ) => { err!( "Program has incomplete sentences" ) }, - nom::Err::Error( nom::error::Error { input, .. } ) | nom::Err::Failure( nom::error::Error { input, .. } ) => { err!( "It is a sentence that can not be parsed: `{}`", input ) } - } - } ) - } - } -} - -// - -crate::mod_interface! -{ - exposed use ProgramParser; -} diff --git a/module/move/wca/src/ca/verifier/command.rs b/module/move/wca/src/ca/verifier/command.rs index 09628321c0..094ac3efac 100644 --- a/module/move/wca/src/ca/verifier/command.rs +++ b/module/move/wca/src/ca/verifier/command.rs @@ -1,24 +1,24 @@ pub( crate ) mod private { use crate::*; - use std::collections::HashMap; /// Represents a grammatically correct command with a phrase descriptor, a list of command subjects, and a set of command options. /// /// # Example: /// /// ``` - /// # use wca::{ VerifiedCommand, Value }; + /// # use wca::{ VerifiedCommand, Value, Args, Props }; /// # use std::collections::HashMap; /// VerifiedCommand /// { /// phrase : "command".to_string(), - /// subjects : vec![ Value::String( "subject_value".to_string() ), /* ... */ ], - /// properties : HashMap::from_iter( + /// internal_command : false, + /// args : Args( vec![ Value::String( "subject_value".to_string() ), /* ... */ ] ), + /// props : Props( HashMap::from_iter( /// [ /// ( "prop_name".to_string(), Value::Number( 42.0 ) ), /// /* ... */ - /// ]) + /// ])) /// }; /// ``` /// @@ -32,9 +32,9 @@ pub( crate ) mod private /// Flag indicating whether a command is internal or not. pub internal_command : bool, /// Command subjects. - pub subjects : Vec< Value >, + pub args : Args, /// Command options. - pub properties : HashMap< String, Value >, + pub props : Props, } } diff --git a/module/move/wca/src/ca/verifier/verifier.rs b/module/move/wca/src/ca/verifier/verifier.rs index 2927cb637a..3c51962e47 100644 --- a/module/move/wca/src/ca/verifier/verifier.rs +++ b/module/move/wca/src/ca/verifier/verifier.rs @@ -6,6 +6,7 @@ pub( crate ) mod private // use former::Former; use std::collections::HashMap; use wtools::{ error, error::Result, err }; + use ca::help::private::{ HelpGeneratorOptions, LevelOfDetail, generate_help_content }; // TODO: Remove Clone /// Converts a `ParsedCommand` to a `VerifiedCommand` by performing validation and type casting on values. @@ -98,11 +99,12 @@ pub( crate ) mod private #[ cfg( feature = "on_unknown_suggest" ) ] fn suggest_command< 'a >( dictionary : &'a Dictionary, user_input: &str ) -> Option< &'a str > { - let jaro = eddie::JaroWinkler::new(); + use textdistance::{ Algorithm, JaroWinkler }; + let jaro = JaroWinkler::default(); let sim = dictionary .commands .iter() - .map( |( name, c )| ( jaro.similarity( name, user_input ), c ) ) + .map( |( name, c )| ( jaro.for_str( name, user_input ).nsim(), c ) ) .max_by( |( s1, _ ), ( s2, _ )| s1.total_cmp( s2 ) ); if let Some(( sim, variant )) = sim { @@ -116,47 +118,32 @@ pub( crate ) mod private None } - fn find_variant< 'a > + fn get_count_from_properties ( - variants: &'a Command, - raw_command : &ParsedCommand, - ) -> Option< &'a Command > + properties : &HashMap< String, ValueDescription >, + properties_aliases : &HashMap< String, String >, + raw_properties : &HashMap< String, String > + ) -> usize { - let mut maybe_valid_variants = vec![]; - - for variant @ Command - { - subjects, - properties, - properties_aliases, - .. - } - in [ variants ] - { - let raw_subjects_count = raw_command.subjects.len(); - let expected_subjects_count = subjects.len(); - if raw_subjects_count > expected_subjects_count { continue; } - - let mut maybe_subjects_count = 0_usize; - for ( k, _v ) in &raw_command.properties - { - if properties.contains_key( k ) { continue; } - if let Some( key ) = properties_aliases.get( k ) - { - if properties.contains_key( key ) { continue; } - } - maybe_subjects_count += 1; - } + raw_properties.iter() + .filter( |( k, _ )| !( properties.contains_key( *k ) || properties_aliases.get( *k ).map_or( false, | key | properties.contains_key( key ) ) ) ) + .count() + } - if raw_subjects_count + maybe_subjects_count > expected_subjects_count { continue; } + fn is_valid_command_variant( subjects_count : usize, raw_count : usize, possible_count : usize ) -> bool + { + raw_count + possible_count <= subjects_count + } - maybe_valid_variants.push( variant ); - } + fn check_command< 'a >( variant : &'a Command, raw_command : &ParsedCommand ) -> Option< &'a Command > + { + let Command { subjects, properties, properties_aliases, .. } = variant; + let raw_subjects_count = raw_command.subjects.len(); + let expected_subjects_count = subjects.len(); + if raw_subjects_count > expected_subjects_count { return None; } - // if maybe_valid_variants.len() == 1 { return Some( maybe_valid_variants[ 0 ] ) } - // qqq: provide better variant selection( E.g. based on types ) - if !maybe_valid_variants.is_empty() { return Some( maybe_valid_variants[ 0 ] ) } - else { None } + let possible_subjects_count = Self::get_count_from_properties( properties, properties_aliases, &raw_command.properties ); + if Self::is_valid_command_variant( expected_subjects_count, raw_subjects_count, possible_subjects_count ) { Some( variant ) } else { None } } // qqq : for Barsik : @@ -447,11 +434,11 @@ pub( crate ) mod private { phrase : raw_command.name, internal_command : true, - subjects : vec![], - properties : HashMap::new(), + args : Args( vec![] ), + props : Props( HashMap::new() ), }); } - let variants = dictionary.command( &raw_command.name ) + let command = dictionary.command( &raw_command.name ) .ok_or_else::< error::for_app::Error, _ > ( || @@ -463,28 +450,13 @@ pub( crate ) mod private } )?; - let Some( cmd ) = Self::find_variant( variants, &raw_command ) else + let Some( cmd ) = Self::check_command( command, &raw_command ) else { error::for_app::bail! ( - "`{}` command with specified subjects not found. Available variants `{:#?}`", + "`{}` command with specified subjects not found. Command info: `{}`", &raw_command.name, - [ variants ] - .into_iter() - .map - ( - | x | - format! - ( - ".{}{}", - &raw_command.name, - { - let variants = x.subjects.iter().filter( | x | !x.optional ).map( | x | format!( "{:?}", x.kind ) ).collect::< Vec< _ > >(); - if variants.is_empty() { String::new() } else { variants.join( "" ) } - } - ) - ) - .collect::< Vec< _ > >() + generate_help_content( dictionary, HelpGeneratorOptions::former().for_commands([ dictionary.command( &raw_command.name ).unwrap() ]).command_prefix( "." ).subject_detailing( LevelOfDetail::Detailed ).form() ).strip_suffix( " " ).unwrap() ); }; @@ -496,8 +468,8 @@ pub( crate ) mod private { phrase : cmd.phrase.to_owned(), internal_command : false, - subjects, - properties, + args : Args( subjects ), + props : Props( properties ), }) } } diff --git a/module/move/wca/tests/assets/wca_hello_test/Cargo.toml b/module/move/wca/tests/assets/wca_hello_test/Cargo.toml deleted file mode 100644 index 745b8fb7db..0000000000 --- a/module/move/wca/tests/assets/wca_hello_test/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "wca_hello_test" -version = "0.1.0" -edition = "2021" -[dependencies] -wca = {{ absolute_path }} diff --git a/module/move/wca/tests/assets/wca_hello_test/src/main.rs b/module/move/wca/tests/assets/wca_hello_test/src/main.rs index a098e8da8d..796622c3e5 100644 --- a/module/move/wca/tests/assets/wca_hello_test/src/main.rs +++ b/module/move/wca/tests/assets/wca_hello_test/src/main.rs @@ -1,17 +1,13 @@ fn main() { - use wca:: - { - CommandsAggregator, Command, Routine, Type, - Args, - }; + use wca::{ Type, VerifiedCommand }; let ca = wca::CommandsAggregator::former() .command( "echo" ) .hint( "prints all subjects and properties" ) .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | args : Args, props | { println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ) } ) + .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) .end() .perform(); diff --git a/module/move/wca/tests/inc/commands_aggregator/basic.rs b/module/move/wca/tests/inc/commands_aggregator/basic.rs index 292cc94ea7..f7019bebf6 100644 --- a/module/move/wca/tests/inc/commands_aggregator/basic.rs +++ b/module/move/wca/tests/inc/commands_aggregator/basic.rs @@ -1,4 +1,5 @@ use super::*; +use the_module::VerifiedCommand; // @@ -35,24 +36,6 @@ tests_impls! a_true!( ca.perform( ".help.command" ).is_err() ); } - fn custom_parser() - { - let parser = Parser::former() - .command_prefix( '-' ) - .form(); - - let ca = CommandsAggregator::former() - .parser( parser ) - .command( "command" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "command" ) ) - .end() - .perform(); - - a_id!( (), ca.perform( "-command" ).unwrap() ); - } - fn dot_command() { let ca = CommandsAggregator::former() @@ -134,7 +117,7 @@ tests_impls! .end() .perform(); - let command = r#".command "./path:to_dir" "#; + let command = vec![ ".command".into(), "./path:to_dir".into() ]; a_id!( (), ca.perform( command ).unwrap() ); @@ -166,17 +149,14 @@ tests_impls! .form() ) .perform(); - let parser = Parser::former().form(); - use the_module::CommandParser; + let parser = Parser; let grammar = the_module::Verifier; let executor = the_module::Executor::former().form(); - let command = r#".command qwe:rty nightly:true "#; - - let raw_command = parser.command( command ).unwrap(); + let raw_command = parser.parse( [ ".command", "qwe:rty", "nightly:true" ] ).unwrap().commands.remove( 0 ); let grammar_command = grammar.to_command( dictionary, raw_command ).unwrap(); - a_id!( grammar_command.subjects, vec![ the_module::Value::String( "qwe:rty".into() ) ] ); + a_id!( grammar_command.args.0, vec![ the_module::Value::String( "qwe:rty".into() ) ] ); a_id!( (), executor.command( dictionary, grammar_command ).unwrap() ); } @@ -196,17 +176,14 @@ tests_impls! ) .form(); - let command = r#".command qwe:rty"#; - - let parser = Parser::former().form(); - use the_module::CommandParser; + let parser = Parser; let grammar = the_module::Verifier; let executor = the_module::Executor::former().form(); - let raw_command = parser.command( command ).unwrap(); + let raw_command = parser.parse( [ ".command", "qwe:rty" ] ).unwrap().commands.remove( 0 ); let grammar_command = grammar.to_command( dictionary, raw_command ).unwrap(); - a_id!( grammar_command.subjects, vec![ the_module::Value::String( "qwe:rty".into() ) ] ); + a_id!( grammar_command.args.0, vec![ the_module::Value::String( "qwe:rty".into() ) ] ); a_id!( (), executor.command( dictionary, grammar_command ).unwrap() ); } @@ -227,43 +204,34 @@ tests_impls! ) .form(); - let command = r#".command qwe:rty"#; - - let parser = Parser::former().form(); - use the_module::CommandParser; + let parser = Parser; let grammar = the_module::Verifier; let executor = the_module::Executor::former().form(); - let raw_command = parser.command( command ).unwrap(); + let raw_command = parser.parse( [ ".command", "qwe:rty" ] ).unwrap().commands.remove( 0 ); let grammar_command = grammar.to_command( dictionary, raw_command ).unwrap(); - a_id!( grammar_command.subjects, vec![ the_module::Value::String("qwe:rty".into()) ] ); + a_id!( grammar_command.args.0, vec![ the_module::Value::String("qwe:rty".into()) ] ); a_id!( (), executor.command( dictionary, grammar_command ).unwrap() ); } // qqq : make the following test work - // fn subject_with_spaces() - // { - // let query = "SELECT title, links, MIN( published ) FROM Frames"; - // let ca = CommandsAggregator::former() - // .grammar( - // [ - // wca::Command::former() - // .hint( "hint" ) - // .long_hint( "long_hint" ) - // .phrase( "query.execute" ) - // .subject( "SQL query", Type::String, false ) - // .form(), - // ]) - // .executor( - // [ - // ( "query.execute".to_owned(), Routine::new( move |( args, _ )| { assert_eq!( query, args.get_owned::< &str >( 0 ).unwrap() ); Ok( () ) } ) ), - // ]) - // .build(); - - // a_id!( (), ca.perform( vec![ ".query.execute".to_string(), query.into() ] ).unwrap() ); - // } + fn subject_with_spaces() + { + let query = "SELECT title, links, MIN( published ) FROM Frames"; + + let ca = CommandsAggregator::former() + .command( "query.execute" ) + .hint( "hint" ) + .long_hint( "long_hint" ) + .subject().hint( "SQL query" ).kind( Type::String ).optional( false ).end() + .routine( move | o : VerifiedCommand | assert_eq!( query, o.args.get_owned::< &str >( 0 ).unwrap() ) ) + .end() + .perform(); + + a_id!( (), ca.perform( vec![ ".query.execute".to_string(), query.into() ] ).unwrap() ); + } } // @@ -272,12 +240,11 @@ tests_index! { simple, with_only_general_help, - custom_parser, dot_command, error_types, path_subject_with_colon, string_subject_with_colon, no_prop_subject_with_colon, optional_prop_subject_with_colon, - // subject_with_spaces, + subject_with_spaces, } diff --git a/module/move/wca/tests/inc/commands_aggregator/help.rs b/module/move/wca/tests/inc/commands_aggregator/help.rs index 542b94ad43..3c4f01f6a6 100644 --- a/module/move/wca/tests/inc/commands_aggregator/help.rs +++ b/module/move/wca/tests/inc/commands_aggregator/help.rs @@ -29,6 +29,8 @@ pub fn start_sync< AP, Args, Arg, P > let args = args.into_iter().map( | a | a.as_ref().into() ).collect::< Vec< std::ffi::OsString > >(); let child = Command::new( application ).args( &args ).stdout( Stdio::piped() ).stderr( Stdio::piped() ).current_dir( path ).spawn().unwrap(); let output = child.wait_with_output().unwrap(); + dbg!( &output ); + String::from_utf8( output.stdout ).unwrap() } diff --git a/module/move/wca/tests/inc/executor/command.rs b/module/move/wca/tests/inc/executor/command.rs index 4d4ade6208..b1dcf7ac12 100644 --- a/module/move/wca/tests/inc/executor/command.rs +++ b/module/move/wca/tests/inc/executor/command.rs @@ -1,4 +1,5 @@ use super::*; +use the_module::VerifiedCommand; // @@ -7,7 +8,7 @@ tests_impls! fn basic() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -24,7 +25,7 @@ tests_impls! let verifier = Verifier; // init executor - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); let executor = Executor::former().form(); @@ -35,7 +36,7 @@ tests_impls! fn with_subject() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -46,7 +47,7 @@ tests_impls! .long_hint( "long_hint" ) .phrase( "command" ) .subject().hint( "hint" ).kind( Type::String ).optional( false ).end() - .routine( | args : Args | args.get( 0 ).map( | a | println!( "{a:?}" )).ok_or_else( || "Subject not found" ) ) + .routine( | o : VerifiedCommand | o.args.get( 0 ).map( | a | println!( "{a:?}" )).ok_or_else( || "Subject not found" ) ) .form() ) .form(); @@ -56,14 +57,14 @@ tests_impls! let executor = Executor::former().form(); // with subject - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // execute the command a_true!( executor.command( dictionary, grammar_command ).is_ok() ); // without subject - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); } @@ -71,7 +72,7 @@ tests_impls! fn with_property() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -82,7 +83,7 @@ tests_impls! .long_hint( "long_hint" ) .phrase( "command" ) .property( "prop" ).hint( "about prop" ).kind( Type::String ).optional( true ).end() - .routine( | props : Props | props.get( "prop" ).map( | a | println!( "{a:?}" )).ok_or_else( || "Prop not found" ) ) + .routine( | o : VerifiedCommand | o.props.get( "prop" ).map( | a | println!( "{a:?}" )).ok_or_else( || "Prop not found" ) ) .form() ) .form(); @@ -92,19 +93,19 @@ tests_impls! let executor = Executor::former().form(); // with property - let raw_command = parser.command( ".command prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // execute the command a_true!( executor.command( dictionary, grammar_command ).is_ok() ); // with subject and without property - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // with subject and with property - let raw_command = parser.command( ".command subject prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); } @@ -114,7 +115,7 @@ tests_impls! use std::sync::{ Arc, Mutex }; // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -136,14 +137,13 @@ tests_impls! ) .form(); let verifier = Verifier; - let mut ctx = wca::Context::default(); - ctx.insert( Arc::new( Mutex::new( 1 ) ) ); + let mut ctx = wca::Context::new( Mutex::new( 1 ) ); // init executor let executor = Executor::former() .context( ctx ) .form(); - let raw_command = parser.command( ".check" ).unwrap(); + let raw_command = parser.parse( [ ".check" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // execute the command @@ -154,7 +154,7 @@ tests_impls! fn without_routine() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -172,7 +172,7 @@ tests_impls! // init executor let executor = Executor::former().form(); - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( executor.command( dictionary, grammar_command ).is_err() ); diff --git a/module/move/wca/tests/inc/executor/mod.rs b/module/move/wca/tests/inc/executor/mod.rs index 6a8ddaec76..6b2906031a 100644 --- a/module/move/wca/tests/inc/executor/mod.rs +++ b/module/move/wca/tests/inc/executor/mod.rs @@ -2,9 +2,8 @@ use super::*; use the_module:: { Parser, - ProgramParser, CommandParser, - Args, Props, Context, Type, + Context, Type, Dictionary, Verifier, diff --git a/module/move/wca/tests/inc/executor/program.rs b/module/move/wca/tests/inc/executor/program.rs index 803060386e..885d2e017e 100644 --- a/module/move/wca/tests/inc/executor/program.rs +++ b/module/move/wca/tests/inc/executor/program.rs @@ -1,4 +1,5 @@ use super::*; +use the_module::VerifiedCommand; // @@ -7,7 +8,7 @@ tests_impls! fn basic() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -27,7 +28,7 @@ tests_impls! let executor = Executor::former().form(); // existed command | unknown command will fail on converter - let raw_program = parser.program( ".command" ).unwrap(); + let raw_program = parser.parse( [ ".command" ] ).unwrap(); let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); // execute the command @@ -40,7 +41,7 @@ tests_impls! use wtools::error::for_app::Error; // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -69,7 +70,7 @@ tests_impls! .subject().hint( "number" ).kind( Type::Number ).optional( true ).end() .routine ( - | ctx : Context, args : Args | + | ctx : Context, o : VerifiedCommand | ctx .get() .ok_or_else( || "Have no value".to_string() ) @@ -78,7 +79,7 @@ tests_impls! | x : Arc< Mutex< i32 > > | { let x = x.lock().unwrap(); - let y : i32 = args.get( 0 ).ok_or_else( || "Missing subject".to_string() ).unwrap().to_owned().into(); + let y : i32 = o.args.get( 0 ).ok_or_else( || "Missing subject".to_string() ).unwrap().to_owned().into(); if dbg!( *x ) != y { Err( format!( "{} not eq {}", x, y ) ) } else { Ok( () ) } } @@ -90,21 +91,20 @@ tests_impls! let verifier = Verifier; // starts with 0 - let mut ctx = wca::Context::default(); - ctx.insert( Arc::new( Mutex::new( 0 ) ) ); + let ctx = wca::Context::new( Mutex::new( 0 ) ); // init simple executor let executor = Executor::former() .context( ctx ) .form(); // value in context = 0 - let raw_program = parser.program( ".eq 1" ).unwrap(); + let raw_program = parser.parse( [ ".eq", "1" ] ).unwrap(); let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); a_true!( executor.program( dictionary, grammar_program ).is_err() ); // value in context = 1 + 1 + 1 = 3 - let raw_program = parser.program( ".eq 0 .inc .inc .eq 2" ).unwrap(); + let raw_program = parser.parse( [ ".eq", "0", ".inc", ".inc", ".eq", "2" ] ).unwrap(); let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); a_true!( executor.program( dictionary, grammar_program ).is_ok() ); diff --git a/module/move/wca/tests/inc/grammar/from_command.rs b/module/move/wca/tests/inc/grammar/from_command.rs index a3923cf832..9823236c0c 100644 --- a/module/move/wca/tests/inc/grammar/from_command.rs +++ b/module/move/wca/tests/inc/grammar/from_command.rs @@ -7,7 +7,7 @@ tests_impls! fn command_validation() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -23,25 +23,25 @@ tests_impls! let verifier = Verifier; // existed command - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // not existed command - let raw_command = parser.command( ".invalid_command" ).unwrap(); + let raw_command = parser.parse( [ ".invalid_command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // invalid command syntax - let raw_command = parser.command( "invalid_command" ); + let raw_command = parser.parse( [ "invalid_command" ] ); a_true!( raw_command.is_err() ); } fn subjects() { // init parser - let parser = Parser::former().form(); + let parser = Parser; let dictionary = &Dictionary::former() .command ( @@ -58,35 +58,35 @@ tests_impls! let verifier = Verifier; // with only one subject - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_id!( vec![ Value::String( "subject".to_string() ) ], grammar_command.subjects ); - a_true!( grammar_command.properties.is_empty() ); + a_id!( vec![ Value::String( "subject".to_string() ) ], grammar_command.args.0 ); + a_true!( grammar_command.props.is_empty() ); // with more subjects that it is set - let raw_command = parser.command( ".command subject1 subject2" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject1", "subject2" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // with subject and property that isn't declared - let raw_command = parser.command( ".command subject prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject", "prop:value" ] ).unwrap().commands.remove( 0 ); a_true!( verifier.to_command( dictionary, raw_command ).is_err() ); // subject with colon when property not declared - let raw_command = parser.command( ".command prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_id!( vec![ Value::String( "prop:value".to_string() ) ], grammar_command.subjects ); - a_true!( grammar_command.properties.is_empty() ); + a_id!( vec![ Value::String( "prop:value".to_string() ) ], grammar_command.args.0 ); + a_true!( grammar_command.props.is_empty() ); } fn subject_type_check() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -103,19 +103,19 @@ tests_impls! let verifier = Verifier; // string when number expected - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // valid negative float number when number expected - let raw_command = parser.command( ".command -3.14" ).unwrap(); + let raw_command = parser.parse( [ ".command", "-3.14" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); } fn subject_with_list() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -132,7 +132,7 @@ tests_impls! let verifier = Verifier; // with only one subject - let raw_command = parser.command( ".command first_subject,second_subject,third_subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "first_subject,second_subject,third_subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( &dictionary, raw_command ).unwrap(); a_id!( vec! @@ -143,14 +143,14 @@ tests_impls! Value::String( "second_subject".into() ), Value::String( "third_subject".into() ), ]) - ], grammar_command.subjects ); - a_true!( grammar_command.properties.is_empty() ); + ], grammar_command.args.0 ); + a_true!( grammar_command.props.is_empty() ); } fn subject_is_optional_basic() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -167,18 +167,18 @@ tests_impls! let verifier = Verifier; // with subject - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // without subject - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); } fn preferred_non_optional_first_order() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -196,20 +196,20 @@ tests_impls! let verifier = Verifier; // second subject is required, but missing - let raw_command = parser.command( ".command 42" ).unwrap(); + let raw_command = parser.parse( [ ".command", "42" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err(), "subject identifies as first subject" ); // first subject is missing - let raw_command = parser.command( ".command valid_string" ).unwrap(); + let raw_command = parser.parse( [ ".command", "valid_string" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // both subjects exists - let raw_command = parser.command( ".command 42 string" ).unwrap(); + let raw_command = parser.parse( [ ".command", "42", "string" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // first subject not a number, but both arguments exists - let raw_command = parser.command( ".command not_a_number string" ).unwrap(); + let raw_command = parser.parse( [ ".command", "not_a_number", "string" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err(), "first subject not a number" ); } @@ -217,7 +217,7 @@ tests_impls! fn properties() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -234,26 +234,26 @@ tests_impls! let verifier = Verifier; // with only one property - let raw_command = parser.command( ".command prop1:value1" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop1:value1" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_true!( grammar_command.subjects.is_empty() ); - a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "value1".to_string() ) ) ]), grammar_command.properties ); + a_true!( grammar_command.args.0.is_empty() ); + a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "value1".to_string() ) ) ]), grammar_command.props.0 ); // with property re-write - let raw_command = parser.command( ".command prop1:value prop1:another_value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop1:value", "prop1:another_value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_true!( grammar_command.subjects.is_empty() ); - a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "another_value".to_string() ) ) ]), grammar_command.properties ); + a_true!( grammar_command.args.0.is_empty() ); + a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "another_value".to_string() ) ) ]), grammar_command.props.0 ); // with undeclareted property - let raw_command = parser.command( ".command undeclareted_prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "undeclareted_prop:value" ] ).unwrap().commands.remove( 0 ); a_true!( verifier.to_command( dictionary, raw_command ).is_err() ); // with undeclareted subject - let raw_command = parser.command( ".command subject prop1:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject", "prop1:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); @@ -262,7 +262,7 @@ tests_impls! fn property_type_check() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -279,19 +279,19 @@ tests_impls! let verifier = Verifier; // string when number expected - let raw_command = parser.command( ".command prop:Property" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:Property" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // valid negative float number when number expected - let raw_command = parser.command( ".command prop:-3.14" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:-3.14" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); } fn property_with_list() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -308,21 +308,21 @@ tests_impls! let verifier = Verifier; // with only one subject - let raw_command = parser.command( ".command prop:1,2,3" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:1,2,3" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_true!( grammar_command.subjects.is_empty() ); + a_true!( grammar_command.args.0.is_empty() ); a_id! ( vec![ 1.0, 2.0, 3.0 ], - Vec::< f64 >::from( grammar_command.properties[ "prop" ].clone() ) + Vec::< f64 >::from( grammar_command.props.0[ "prop" ].clone() ) ); } fn alias_property() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -345,25 +345,25 @@ tests_impls! let verifier = Verifier; // basic - let raw_command = parser.command( ".command property:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "property:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_true!( grammar_command.subjects.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.properties ); + a_true!( grammar_command.args.0.is_empty() ); + a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); // first alias - let raw_command = parser.command( ".command prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_true!( grammar_command.subjects.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.properties ); + a_true!( grammar_command.args.0.is_empty() ); + a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); // second alias - let raw_command = parser.command( ".command p:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "p:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_true!( grammar_command.subjects.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.properties ); + a_true!( grammar_command.args.0.is_empty() ); + a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); // init converter with layered properties let dictionary = &Dictionary::former() @@ -380,11 +380,11 @@ tests_impls! .form(); let verifier = Verifier; - let raw_command = parser.command( ".command p:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "p:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); - a_true!( grammar_command.subjects.is_empty() ); - a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.properties ); + a_true!( grammar_command.args.0.is_empty() ); + a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); } } diff --git a/module/move/wca/tests/inc/grammar/from_program.rs b/module/move/wca/tests/inc/grammar/from_program.rs index 15aa827281..670eaf178c 100644 --- a/module/move/wca/tests/inc/grammar/from_program.rs +++ b/module/move/wca/tests/inc/grammar/from_program.rs @@ -6,7 +6,7 @@ tests_impls! { fn basic() { - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -32,21 +32,21 @@ tests_impls! let verifier = Verifier; // parse program with only one command - let raw_program = parser.program( ".command1 subject" ).unwrap(); + let raw_program = parser.parse( [ ".command1", "subject" ] ).unwrap(); // convert program let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); a_true!( grammar_program.commands.len() == 1 ); - a_id!( vec![ Value::String( "subject".to_string() ) ], grammar_program.commands[ 0 ].subjects ); + a_id!( vec![ Value::String( "subject".to_string() ) ], grammar_program.commands[ 0 ].args.0 ); // parse program several commands - let raw_program = parser.program( ".command1 first_subj .command2 second_subj" ).unwrap(); + let raw_program = parser.parse( [ ".command1", "first_subj", ".command2", "second_subj" ] ).unwrap(); // convert program let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); a_true!( grammar_program.commands.len() == 2 ); - a_id!( vec![ Value::String( "first_subj".to_string() ) ], grammar_program.commands[ 0 ].subjects ); - a_id!( vec![ Value::String( "second_subj".to_string() ) ], grammar_program.commands[ 1 ].subjects ); + a_id!( vec![ Value::String( "first_subj".to_string() ) ], grammar_program.commands[ 0 ].args.0 ); + a_id!( vec![ Value::String( "second_subj".to_string() ) ], grammar_program.commands[ 1 ].args.0 ); } } diff --git a/module/move/wca/tests/inc/grammar/mod.rs b/module/move/wca/tests/inc/grammar/mod.rs index 679a096afe..38c94dc114 100644 --- a/module/move/wca/tests/inc/grammar/mod.rs +++ b/module/move/wca/tests/inc/grammar/mod.rs @@ -2,7 +2,6 @@ use super::*; use the_module:: { Parser, - ProgramParser, CommandParser, Type, Value, Dictionary, diff --git a/module/move/wca/tests/inc/parser/command.rs b/module/move/wca/tests/inc/parser/command.rs index ff75539190..986ab1d0c0 100644 --- a/module/move/wca/tests/inc/parser/command.rs +++ b/module/move/wca/tests/inc/parser/command.rs @@ -6,7 +6,7 @@ tests_impls! { fn basic() { - let parser = Parser::former().form(); + let parser = Parser; // only command a_id! @@ -17,7 +17,7 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( ".command" ).unwrap() + parser.parse( [ ".command" ] ).unwrap().commands[ 0 ] ); // command with one subject @@ -29,7 +29,7 @@ tests_impls! subjects : vec![ "subject".into() ], properties : HashMap::new(), }, - parser.command( ".command subject" ).unwrap() + parser.parse( [ ".command", "subject" ] ).unwrap().commands[ 0 ] ); // command with many subjects @@ -41,7 +41,7 @@ tests_impls! subjects : vec![ "subject1".into(), "subject2".into(), "subject3".into() ], properties : HashMap::new(), }, - parser.command( ".command subject1 subject2 subject3" ).unwrap() + parser.parse( [ ".command", "subject1", "subject2", "subject3" ] ).unwrap().commands[ 0 ] ); // command with one property @@ -53,7 +53,7 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), }, - parser.command( ".command prop:value" ).unwrap() + parser.parse( [ ".command", "prop:value" ] ).unwrap().commands[ 0 ] ); // command with many properties @@ -70,7 +70,7 @@ tests_impls! ( "prop3".into(), "value3".into() ) ]), }, - parser.command( ".command prop1:value1 prop2:value2 prop3:value3" ).unwrap() + parser.parse( [ ".command", "prop1:value1", "prop2:value2", "prop3:value3" ] ).unwrap().commands[ 0 ] ); // command with one subject and one property @@ -82,7 +82,7 @@ tests_impls! subjects : vec![ "subject".into() ], properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), }, - parser.command( ".command subject prop:value" ).unwrap() + parser.parse( [ ".command", "subject", "prop:value" ] ).unwrap().commands[ 0 ] ); // command with many subjects and many properties @@ -104,23 +104,25 @@ tests_impls! ( "prop3".into(), "value3".into() ), ]), }, - parser.command( ".command subject1 subject2 subject3 prop1:value1 prop2:value2 prop3:value3" ).unwrap() + parser.parse( [ ".command", "subject1", "subject2", "subject3", "prop1:value1", "prop2:value2", "prop3:value3" ] ).unwrap().commands[ 0 ] ); } - fn with_spaces() + // aaa : the parser must be able to accept a list of arguments(std::env::args()) + // aaa : yep + fn with_spaces_in_value() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( ParsedCommand { name : "command".into(), - subjects : vec![], + subjects : vec![ "value with spaces".into() ], properties : HashMap::new(), }, - parser.command( " .command " ).unwrap() + parser.parse( [ ".command", "value with spaces" ] ).unwrap().commands[ 0 ] ); a_id! @@ -128,10 +130,10 @@ tests_impls! ParsedCommand { name : "command".into(), - subjects : vec![ "subject".into() ], - properties : HashMap::new(), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), }, - parser.command( " .command subject " ).unwrap() + parser.parse( [ ".command", "prop:value with spaces" ] ).unwrap().commands[ 0 ] ); a_id! @@ -139,66 +141,38 @@ tests_impls! ParsedCommand { name : "command".into(), - subjects : vec![ "subject".into() ], - properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), }, - parser.command( " .command subject prop:value " ).unwrap() + parser.parse( [ ".command", "prop:", "value with spaces" ] ).unwrap().commands[ 0 ] + ); + + a_id! + ( + ParsedCommand + { + name : "command".into(), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + }, + parser.parse( [ ".command", "prop", ":value with spaces" ] ).unwrap().commands[ 0 ] ); - } - // qqq : the parser must be able to accept a list of arguments(std::env::args()) - // fn with_spaces_in_value() - // { - // let parser = Parser::former().form(); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![ "value with spaces".into() ], - // properties : HashMap::new(), - // }, - // parser.command( vec![ ".command".to_string(), "value with spaces".into() ] ).unwrap() - // ); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![], - // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), - // }, - // parser.command( vec![ ".command".to_string(), "prop:value with spaces".into() ] ).unwrap() - // ); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![], - // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), - // }, - // parser.command( vec![ ".command".to_string(), "prop:".into(), "value with spaces".into() ] ).unwrap() - // ); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![], - // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), - // }, - // parser.command( vec![ ".command".to_string(), "prop".into(), ":".into(), "value with spaces".into() ] ).unwrap() - // ); - // } + a_id! + ( + ParsedCommand + { + name : "command".into(), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + }, + parser.parse( [ ".command", "prop", ":", "value with spaces" ] ).unwrap().commands[ 0 ] + ); + } fn not_only_alphanumeric_symbols() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -208,7 +182,7 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( ".additional_command" ).unwrap() + parser.parse( [ ".additional_command" ] ).unwrap().commands[ 0 ] ); a_id! @@ -219,7 +193,7 @@ tests_impls! subjects : vec![ "subj_ect".into() ], properties : HashMap::new(), }, - parser.command( ".command.sub_command subj_ect" ).unwrap() + parser.parse( [ ".command.sub_command", "subj_ect" ] ).unwrap().commands[ 0 ] ); a_id! @@ -230,32 +204,13 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "long_prop".into(), "some-value".into() ) ]), }, - parser.command( ".command long_prop:some-value" ).unwrap() - ); - } - - fn same_command_and_prop_delimeter() - { - let parser = Parser::former() - .command_prefix( '-' ) - .prop_delimeter( '-' ) - .form(); - - a_id! - ( - ParsedCommand - { - name : "command".into(), - subjects : vec![ "subject".into() ], - properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), - }, - parser.command( "-command subject prop-value" ).unwrap() + parser.parse( [ ".command", "long_prop:some-value" ] ).unwrap().commands[ 0 ] ); } fn path_in_subject() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -265,7 +220,7 @@ tests_impls! subjects : vec![ "/absolute/path/to/something".into() ], properties : HashMap::new(), }, - parser.command( ".command /absolute/path/to/something" ).unwrap() + parser.parse( [ ".command", "/absolute/path/to/something" ] ).unwrap().commands[ 0 ] ); a_id! @@ -276,13 +231,13 @@ tests_impls! subjects : vec![ "./path/to/something".into() ], properties : HashMap::new(), }, - parser.command( ".command ./path/to/something" ).unwrap() + parser.parse( [ ".command", "./path/to/something" ] ).unwrap().commands[ 0 ] ); } fn path_in_property() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -292,7 +247,7 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "path".into(), "/absolute/path/to/something".into() ) ]), }, - parser.command( ".command path:/absolute/path/to/something" ).unwrap() + parser.parse( [ ".command", "path:/absolute/path/to/something" ] ).unwrap().commands[ 0 ] ); a_id! @@ -303,7 +258,7 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "path".into(), "./path/to/something".into() ) ]), }, - parser.command( ".command path:./path/to/something" ).unwrap() + parser.parse( [ ".command", "path:./path/to/something" ] ).unwrap().commands[ 0 ] ); a_id! @@ -314,28 +269,13 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "path".into(), "../path/to/something".into() ) ]), }, - parser.command( ".command path:../path/to/something" ).unwrap() - ); - - let parser = Parser::former() - .command_prefix( '/' ) - .form(); - - a_id! - ( - ParsedCommand - { - name : "command".into(), - subjects : vec![], - properties : HashMap::from_iter([ ( "path".into(), "/absolute/path/to/something".into() ) ]), - }, - parser.command( "/command path:/absolute/path/to/something" ).unwrap() + parser.parse( [ ".command", "path:../path/to/something" ] ).unwrap().commands[ 0 ] ); } fn list_in_property() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -345,13 +285,13 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "list".into(), "[1,2,3]".into() ) ]), }, - parser.command( ".command list:[1,2,3]" ).unwrap() + parser.parse( [ ".command", "list:[1,2,3]" ] ).unwrap().commands[ 0 ] ); } fn string_value() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -361,7 +301,7 @@ tests_impls! subjects : vec![ "subject with spaces".into() ], properties : HashMap::from_iter([ ( "prop".into(), "property with spaces".into() ) ]), }, - parser.command( r#".command "subject with spaces" prop:"property with spaces""# ).unwrap() + parser.parse( [ ".command", "subject with spaces", "prop:property with spaces" ] ).unwrap().commands[ 0 ] ); // command in subject and property @@ -370,10 +310,10 @@ tests_impls! ParsedCommand { name : "command".into(), - subjects : vec![ ".command".into() ], + subjects : vec![ "\\.command".into() ], properties : HashMap::from_iter([ ( "prop".into(), ".command".into() ) ]), }, - parser.command( r#".command ".command" prop:".command""# ).unwrap() + parser.parse( [ ".command", "\\.command", "prop:.command" ] ).unwrap().commands[ 0 ] ); // with escaped quetes @@ -385,15 +325,14 @@ tests_impls! subjects : vec![ "' queted ' \\ value".into() ], properties : HashMap::from_iter([ ( "prop".into(), "some \"quetes\" ' \\ in string".into() ) ]), }, - parser.command( r#".command '\' queted \' \\ value' prop:"some \"quetes\" ' \\ in string""# ).unwrap() + parser.parse( [ ".command", "\' queted \' \\ value", "prop:some \"quetes\" ' \\ in string" ] ).unwrap().commands[ 0 ] ); } fn dot_command() { - let parser = Parser::former().form(); + let parser = Parser; - // single dot a_id! ( ParsedCommand @@ -402,10 +341,9 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( "." ).unwrap() + parser.parse( [ "." ] ).unwrap().commands[ 0 ] ); - // command . a_id! ( ParsedCommand @@ -414,7 +352,29 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( ".command." ).unwrap() + parser.parse( [ ".command." ] ).unwrap().commands[ 0 ] + ); + + a_id! + ( + ParsedCommand + { + name : ".?".into(), + subjects : vec![], + properties : HashMap::new(), + }, + parser.parse( [ ".?" ] ).unwrap().commands[ 0 ] + ); + + a_id! + ( + ParsedCommand + { + name : "command.?".into(), + subjects : vec![], + properties : HashMap::new(), + }, + parser.parse( [ ".command.?" ] ).unwrap().commands[ 0 ] ); } } @@ -424,10 +384,8 @@ tests_impls! tests_index! { basic, - with_spaces, - // with_spaces_in_value, + with_spaces_in_value, not_only_alphanumeric_symbols, - same_command_and_prop_delimeter, path_in_subject, path_in_property, list_in_property, diff --git a/module/move/wca/tests/inc/parser/mod.rs b/module/move/wca/tests/inc/parser/mod.rs index 103789b48e..456679d11a 100644 --- a/module/move/wca/tests/inc/parser/mod.rs +++ b/module/move/wca/tests/inc/parser/mod.rs @@ -4,7 +4,6 @@ use wca:: Program, ParsedCommand, Parser, - ProgramParser, CommandParser, }; mod command; diff --git a/module/move/wca/tests/inc/parser/program.rs b/module/move/wca/tests/inc/parser/program.rs index 39dbb64d20..081f8cc3e8 100644 --- a/module/move/wca/tests/inc/parser/program.rs +++ b/module/move/wca/tests/inc/parser/program.rs @@ -6,7 +6,7 @@ tests_impls! { fn basic() { - let parser = Parser::former().form(); + let parser = Parser; // only one command a_id! @@ -20,7 +20,7 @@ tests_impls! properties : HashMap::new(), }, ]}, - parser.program( ".command" ).unwrap() + parser.parse( [ ".command" ] ).unwrap() ); a_id! @@ -46,7 +46,7 @@ tests_impls! properties : HashMap::new(), } ]}, - parser.program( ".command1 .command2 .command3" ).unwrap() + parser.parse( [ ".command1", ".command2", ".command3" ] ).unwrap() ); } } diff --git a/module/move/willbe/Readme.md b/module/move/willbe/Readme.md index 14493705d1..b387b877c6 100644 --- a/module/move/willbe/Readme.md +++ b/module/move/willbe/Readme.md @@ -2,8 +2,7 @@ # Module:: willbe - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml)[![docs.rs](https://img.shields.io/docsrs/willbe?color=e3e8f0&logo=docs.rs)](https://docs.rs/willbe)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwillbe_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20willbe_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_willbe_push.yml) [![docs.rs](https://img.shields.io/docsrs/willbe?color=e3e8f0&logo=docs.rs)](https://docs.rs/willbe) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Utility to publish multi-crate and multi-workspace environments and maintain their consistency. diff --git a/module/move/willbe/src/action/list.rs b/module/move/willbe/src/action/list.rs index b8b25c9369..6e4bd01dc0 100644 --- a/module/move/willbe/src/action/list.rs +++ b/module/move/willbe/src/action/list.rs @@ -176,7 +176,7 @@ mod private /// It holds essential information about the project dependencies. It is also capable /// of holding any nested dependencies in a recursive manner, allowing the modeling /// of complex dependency structures. - #[ derive( Debug, Clone ) ] + #[ derive( Debug, Clone, Eq, PartialEq ) ] pub struct ListNodeReport { /// This could be the name of the library or crate. @@ -186,6 +186,8 @@ mod private /// The path to the node's source files in the local filesystem. This is /// optional as not all nodes may have a local presence (e.g., nodes representing remote crates). pub path : Option< PathBuf >, + /// This field is a flag indicating whether the Node is a duplicate or not. + pub duplicate : bool, /// A list that stores normal dependencies. /// Each element in the list is also of the same 'ListNodeReport' type to allow /// storage of nested dependencies. @@ -218,6 +220,7 @@ mod private write!( f, "{}", self.name )?; if let Some( version ) = &self.version { write!( f, " {version}" )? } if let Some( path ) = &self.path { write!( f, " {}", path.display() )? } + if self.duplicate { write!( f, "(*)" )? } write!( f, "\n" )?; let mut new_spacer = format!( "{spacer}{} ", if self.normal_dependencies.len() < 2 { " " } else { UTF8_SYMBOLS.down } ); @@ -336,6 +339,7 @@ mod private name : dep.name().clone(), version : if args.info.contains( &PackageAdditionalInfo::Version ) { Some( dep.req().to_string() ) } else { None }, path : if args.info.contains( &PackageAdditionalInfo::Path ) { dep.path().as_ref().map( | p | p.clone().into_std_path_buf() ) } else { None }, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -345,7 +349,7 @@ mod private // if this is a cycle (we have visited this node before) if visited.contains( &dep_id ) { - dep_rep.name = format!( "{} (*)", dep_rep.name ); + dep_rep.duplicate = true; return dep_rep; } @@ -404,6 +408,7 @@ mod private name : package.name().to_string(), version : if args.info.contains( &PackageAdditionalInfo::Version ) { Some( package.version().to_string() ) } else { None }, path : if args.info.contains( &PackageAdditionalInfo::Path ) { Some( package.manifest_path().as_std_path().to_path_buf() ) } else { None }, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -413,9 +418,9 @@ mod private *report = match report { - ListReport::Tree(ref mut v ) => ListReport::Tree( { v.extend([ package_report ]); v.clone() } ), + ListReport::Tree( ref mut v ) => ListReport::Tree( { v.extend([ package_report ]); v.clone() } ), ListReport::Empty => ListReport::Tree( vec![ package_report ] ), - ListReport::List(_ ) => unreachable!(), + ListReport::List( _ ) => unreachable!(), }; }; match args.format @@ -423,7 +428,10 @@ mod private ListFormat::Tree if is_package => { let mut visited = HashSet::new(); - tree_package_report( manifest.manifest_path, &mut report, &mut visited ) + tree_package_report( manifest.manifest_path, &mut report, &mut visited ); + let ListReport::Tree( tree ) = report else { unreachable!() }; + let tree = rearrange_duplicates( merge_dev_dependencies( merge_build_dependencies( tree ) ) ); + report = ListReport::Tree( tree ); } ListFormat::Tree => { @@ -433,6 +441,9 @@ mod private { tree_package_report( package.manifest_path().as_std_path().try_into().unwrap(), &mut report, &mut visited ) } + let ListReport::Tree( tree ) = report else { unreachable!() }; + let tree = merge_dev_dependencies( merge_build_dependencies( tree ) ); + report = ListReport::Tree( tree ); } ListFormat::Topological => { @@ -461,7 +472,7 @@ mod private let packages_map = packages::filter ( packages.as_slice(), - FilterMapOptions{ dependency_filter : Some( Box::new( dep_filter ) ), ..Default::default() } + FilterMapOptions { dependency_filter : Some( Box::new( dep_filter ) ), ..Default::default() } ); let graph = graph::construct( &packages_map ); @@ -504,7 +515,7 @@ mod private let node = graph.node_indices().find( | n | graph.node_weight( *n ).unwrap() == &&root_crate ).unwrap(); let mut dfs = Dfs::new( &graph, node ); let mut subgraph = Graph::new(); - let mut node_map = std::collections::HashMap::new(); + let mut node_map = HashMap::new(); while let Some( n )= dfs.next( &graph ) { node_map.insert( n, subgraph.add_node( graph[ n ] ) ); @@ -547,6 +558,114 @@ mod private Ok( report ) } + + fn merge_build_dependencies( mut report: Vec< ListNodeReport > ) -> Vec< ListNodeReport > + { + let mut build_dependencies = vec![]; + for node_report in &mut report + { + build_dependencies = merge_build_dependencies_impl( node_report, build_dependencies ); + } + if let Some( last_report ) = report.last_mut() + { + last_report.build_dependencies = build_dependencies; + } + + report + } + + fn merge_build_dependencies_impl( report : &mut ListNodeReport, mut build_deps_acc : Vec< ListNodeReport > ) -> Vec< ListNodeReport > + { + for dep in report.normal_dependencies.iter_mut() + .chain( report.dev_dependencies.iter_mut() ) + .chain( report.build_dependencies.iter_mut() ) + { + build_deps_acc = merge_build_dependencies_impl(dep, build_deps_acc ); + } + + for dep in std::mem::take( &mut report.build_dependencies ) + { + if !build_deps_acc.contains( &dep ) + { + build_deps_acc.push( dep ); + } + } + + build_deps_acc + } + + fn merge_dev_dependencies( mut report: Vec< ListNodeReport > ) -> Vec< ListNodeReport > + { + let mut dev_dependencies = vec![]; + for node_report in &mut report + { + dev_dependencies = merge_dev_dependencies_impl( node_report, dev_dependencies ); + } + if let Some( last_report ) = report.last_mut() + { + last_report.dev_dependencies = dev_dependencies; + } + + report + } + + fn merge_dev_dependencies_impl( report : &mut ListNodeReport, mut dev_deps_acc : Vec< ListNodeReport > ) -> Vec< ListNodeReport > + { + for dep in report.normal_dependencies.iter_mut() + .chain( report.dev_dependencies.iter_mut() ) + .chain( report.build_dependencies.iter_mut() ) + { + dev_deps_acc = merge_dev_dependencies_impl( dep, dev_deps_acc ); + } + + for dep in std::mem::take( &mut report.dev_dependencies ) + { + if !dev_deps_acc.contains( &dep ) + { + dev_deps_acc.push( dep ); + } + } + + dev_deps_acc + } + + fn rearrange_duplicates( mut report : Vec< ListNodeReport > ) -> Vec< ListNodeReport > + { + let mut required_normal : HashMap< usize, Vec< ListNodeReport > > = HashMap::new(); + for i in 0 .. report.len() + { + let ( required, exist ) : ( Vec< _ >, Vec< _ > ) = std::mem::take( &mut report[ i ].normal_dependencies ).into_iter().partition( | d | d.duplicate ); + report[ i ].normal_dependencies = exist; + required_normal.insert( i, required ); + } + + rearrange_duplicates_resolver( &mut report, &mut required_normal ); + for ( i, deps ) in required_normal + { + report[ i ].normal_dependencies.extend( deps ); + } + + report + } + + fn rearrange_duplicates_resolver( report : &mut [ ListNodeReport ], required : &mut HashMap< usize, Vec< ListNodeReport > > ) + { + for node in report + { + rearrange_duplicates_resolver( &mut node.normal_dependencies, required ); + rearrange_duplicates_resolver( &mut node.dev_dependencies, required ); + rearrange_duplicates_resolver( &mut node.build_dependencies, required ); + + if !node.duplicate + { + if let Some( r ) = required.iter_mut().flat_map( |( _, v )| v ) + .find( | r | r.name == node.name && r.version == node.version && r.path == node.path ) + { + std::mem::swap( r, node ); + } + } + } + } } // diff --git a/module/move/willbe/src/action/main_header.rs b/module/move/willbe/src/action/main_header.rs index 288aa78cba..80ec04b352 100644 --- a/module/move/willbe/src/action/main_header.rs +++ b/module/move/willbe/src/action/main_header.rs @@ -78,7 +78,7 @@ mod private ( format! ( - r#"[![{}](https://img.shields.io/github/actions/workflow/status/{}/StandardRustScheduled.yml?branch=master&label={}&logo=github)](https://github.com/{}/actions/workflows/StandardRustStatus.yml){} + r#"[![{}](https://img.shields.io/github/actions/workflow/status/{}/auto_merge_to_beta.yml?branch=master&label={}&logo=github)](https://github.com/{}/actions/workflows/auto_merge_to_beta.yml){} [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2F{}_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20{}_trivial/https://github.com/{}) [![docs.rs](https://raster.shields.io/static/v1?label=docs&message=online&color=eee&logo=docsdotrs&logoColor=eee)](https://docs.rs/{})"#, self.master_branch, url::git_info_extract( &self.repository_url )?, self.master_branch, url::git_info_extract( &self.repository_url )?, diff --git a/module/move/willbe/src/action/publish.rs b/module/move/willbe/src/action/publish.rs index 94a6e940c6..69ce35400d 100644 --- a/module/move/willbe/src/action/publish.rs +++ b/module/move/willbe/src/action/publish.rs @@ -18,8 +18,6 @@ mod private { /// Represents the absolute path to the root directory of the workspace. pub workspace_root_dir : Option< AbsolutePath >, - /// Represents a collection of packages that are roots of the trees. - pub wanted_to_publish : Vec< CrateDir >, pub plan : Option< package::PublishPlan >, /// Represents a collection of packages and their associated publishing reports. pub packages : Vec<( AbsolutePath, package::PublishReport )> @@ -34,16 +32,8 @@ mod private write!( f, "Nothing to publish" )?; return Ok( () ); } - if let Some( plan ) = &self.plan - { - write!( f, "Tree{} :\n", if self.wanted_to_publish.len() > 1 { "s" } else { "" } )?; - plan.display_as_tree( f, &self.wanted_to_publish )?; - - writeln!( f, "The following packages are pending for publication :" )?; - plan.display_as_list( f )?; - } - writeln!( f, "\nActions :" )?; + writeln!( f, "Actions :" )?; for ( path, report ) in &self.packages { let report = report.to_string().replace("\n", "\n "); @@ -58,66 +48,105 @@ mod private }; write!( f, "Publishing crate by `{}` path\n {report}", path.display() )?; } + if let Some( plan ) = &self.plan + { + if !plan.dry + { + let expected_to_publish = plan + .plans + .iter() + .map( | p | ( p.version_bump.crate_dir.absolute_path(), p.package_name.clone(), p.version_bump.clone() ) ) + .collect::< Vec< _ > >(); + let mut actually_published = self.packages.iter() + .filter_map + ( + |( path, repo )| + if repo.publish.as_ref().is_some_and( | r | r.error.is_ok() ) + { + Some( path.clone() ) + } + else + { + None + } + ) + .collect::< Vec< _ > >(); + + writeln!( f, "Status :" )?; + for ( path, name, version ) in expected_to_publish + { + if let Some( pos ) = actually_published.iter().position( | p | p == &path ) + { + write!( f, "✅ {name} {}", version.new_version )?; + // want to check that only expected packages actually published + _ = actually_published.remove( pos ); + } + else + { + write!( f, "❌ {name} {}", version.old_version )?; + } + } + if !actually_published.is_empty() + { + writeln!( f, "Logical error. Published unexpected packages" )?; + return Err( std::fmt::Error ); + } + } + } Ok( () ) } } + /// Publishes packages based on the specified patterns. /// - /// Publish packages. + /// # Arguments + /// * `patterns` - A vector of patterns specifying the folders to search for packages. + /// * `dry` - A boolean value indicating whether to perform a dry run. + /// * `temp` - A boolean value indicating whether to use a temporary directory. /// - + /// # Returns + /// A Result containing a `PublishPlan` if successful, or an `Error` otherwise. #[ cfg_attr( feature = "tracing", tracing::instrument ) ] - pub fn publish( patterns : Vec< String >, dry : bool, temp : bool ) -> Result< PublishReport, ( PublishReport, Error ) > + pub fn publish_plan( patterns : Vec< String >, dry : bool, temp : bool ) -> Result< package::PublishPlan, Error > { - let mut report = PublishReport::default(); - let mut paths = HashSet::new(); // find all packages by specified folders for pattern in &patterns { - let current_path = AbsolutePath::try_from( std::path::PathBuf::from( pattern ) ).err_with( || report.clone() )?; + let current_path = AbsolutePath::try_from( std::path::PathBuf::from( pattern ) )?; // let current_paths = files::find( current_path, &[ "Cargo.toml" ] ); paths.extend( Some( current_path ) ); } let mut metadata = if paths.is_empty() { - Workspace::from_current_path().err_with( || report.clone() )? + Workspace::from_current_path()? } else { // FIX : patterns can point to different workspaces. Current solution take first random path from list let current_path = paths.iter().next().unwrap().clone(); - let dir = CrateDir::try_from( current_path ).err_with( || report.clone() )?; + let dir = CrateDir::try_from( current_path )?; - Workspace::with_crate_dir( dir ).err_with( || report.clone() )? + Workspace::with_crate_dir( dir )? }; let workspace_root_dir : AbsolutePath = metadata - .workspace_root() - .err_with( || report.clone() )? - .try_into() - .err_with( || report.clone() )?; - report.workspace_root_dir = Some( workspace_root_dir.clone() ); - let packages = metadata.load().err_with( || report.clone() )?.packages().err_with( || report.clone() )?; + .workspace_root()? + .try_into()?; + let packages = metadata.load()?.packages()?; let packages_to_publish : Vec< _ > = packages .iter() .filter( | &package | paths.contains( &AbsolutePath::try_from( package.manifest_path().as_std_path().parent().unwrap() ).unwrap() ) ) .map( | p | p.name().clone() ) .collect(); let package_map = packages.into_iter().map( | p | ( p.name().clone(), Package::from( p.clone() ) ) ).collect::< HashMap< _, _ > >(); - { - for node in &packages_to_publish - { - report.wanted_to_publish.push( package_map.get( node ).unwrap().crate_dir() ); - } - } let graph = metadata.graph(); let subgraph_wanted = graph::subgraph( &graph, &packages_to_publish ); let tmp = subgraph_wanted.map( | _, n | graph[ *n ].clone(), | _, e | graph[ *e ].clone() ); - let mut unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name().err_with( || report.clone() )? ); + let mut unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name()? ); let dir = if temp { @@ -125,11 +154,11 @@ mod private while temp_dir.exists() { - unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name().err_with( || report.clone() )? ); + unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name()? ); temp_dir = env::temp_dir().join( unique_name ); } - fs::create_dir( &temp_dir ).err_with( || report.clone() )?; + fs::create_dir( &temp_dir )?; Some( temp_dir ) } else @@ -137,17 +166,34 @@ mod private None }; - let subgraph = graph::remove_not_required_to_publish( &package_map, &tmp, &packages_to_publish, dir.clone() ); + let subgraph = graph::remove_not_required_to_publish( &package_map, &tmp, &packages_to_publish, dir.clone() )?; let subgraph = subgraph.map( | _, n | n, | _, e | e ); let queue = graph::toposort( subgraph ).unwrap().into_iter().map( | n | package_map.get( &n ).unwrap() ).cloned().collect::< Vec< _ > >(); + let roots = packages_to_publish.iter().map( | p | package_map.get( p ).unwrap().crate_dir() ).collect::< Vec< _ > >(); + let plan = package::PublishPlan::former() .workspace_dir( CrateDir::try_from( workspace_root_dir ).unwrap() ) .option_base_temp_dir( dir.clone() ) .dry( dry ) + .roots( roots ) .packages( queue ) .form(); + + Ok( plan ) + } + + /// + /// Publish packages. + /// + + #[ cfg_attr( feature = "tracing", tracing::instrument ) ] + pub fn publish( plan : package::PublishPlan ) -> Result< PublishReport, ( PublishReport, Error ) > + { + let mut report = PublishReport::default(); + let temp = plan.base_temp_dir.clone(); + report.plan = Some( plan.clone() ); for package_report in package::perform_packages_publish( plan ).err_with( || report.clone() )? { @@ -155,9 +201,9 @@ mod private report.packages.push(( AbsolutePath::try_from( path ).unwrap(), package_report )); } - if temp + if let Some( dir ) = temp { - fs::remove_dir_all( dir.unwrap() ).err_with( || report.clone() )?; + fs::remove_dir_all( dir ).err_with( || report.clone() )?; } Ok( report ) @@ -188,6 +234,8 @@ mod private crate::mod_interface! { - /// Publish package. + /// Create a plan for publishing packages + orphan use publish_plan; + /// Execute the publication plan orphan use publish; } diff --git a/module/move/willbe/src/action/publish_diff.rs b/module/move/willbe/src/action/publish_diff.rs index 010d9c7ead..9d21fec7ec 100644 --- a/module/move/willbe/src/action/publish_diff.rs +++ b/module/move/willbe/src/action/publish_diff.rs @@ -4,11 +4,16 @@ mod private use crate::*; use std::path::PathBuf; + use std::collections::HashMap; + use std::fmt::Formatter; + use colored::Colorize; use crates_tools::CrateArchive; + use action::list::{ ListReport, ListNodeReport }; use _path::AbsolutePath; use wtools::error::for_app::Result; use diff::{ DiffReport, crate_diff }; + use error_tools::for_app::format_err; /// Options for `publish_diff` command #[ derive( Debug, former::Former ) ] @@ -17,38 +22,120 @@ mod private path : PathBuf, keep_archive : Option< PathBuf >, } + + #[ derive( Debug ) ] + pub struct PublishDiffReport + { + pub diffs : HashMap< AbsolutePath, DiffReport >, + pub root_path : AbsolutePath, + pub tree : ListNodeReport, + } + + impl std::fmt::Display for PublishDiffReport + { + fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + { + let mut tree = self.tree.clone(); + let root_path = tree.path.as_ref().unwrap().clone(); + let root_name = tree.name.clone(); + let root_version = tree.version.as_ref().unwrap().clone(); + + fn modify( diffs : &HashMap< AbsolutePath, DiffReport >, tree : &mut ListNodeReport ) + { + let path = tree.path.take().unwrap(); + let path = path.as_path().to_string_lossy(); + let path = path.strip_suffix( "Cargo.toml" ).unwrap_or( &path ); + let root = AbsolutePath::try_from( path ).unwrap(); + + let diff = diffs.get( &root ).unwrap(); + + let has_changes = diff.has_changes(); + tree.name = if has_changes { format!( "{}", tree.name.yellow() ) } else { tree.name.clone() }; + tree.version.as_mut().map( | v | *v = format!( "{} {}", if has_changes { v.yellow() } else { v.as_str().into() }, if has_changes { "MODIFIED" } else { "" } ) ); + + for dep in &mut tree.normal_dependencies + { + modify( diffs, dep ) + } + } + modify( &self.diffs, &mut tree ); + + let path = root_path.as_path().to_string_lossy(); + let path = path.strip_suffix( "Cargo.toml" ).unwrap_or( &path ); + let root = AbsolutePath::try_from( path ).unwrap(); + let diff = self.diffs.get( &root ).unwrap(); + + write!( f, "Tree:\n{}\nChanges in `{root_name} {root_version}`:\n{}", tree, diff )?; + + Ok( () ) + } + } /// Return the differences between a local and remote package versions. #[ cfg_attr( feature = "tracing", tracing::instrument ) ] - pub fn publish_diff( o : PublishDiffOptions ) -> Result< DiffReport > + pub fn publish_diff( o : PublishDiffOptions ) -> Result< PublishDiffReport > { let path = AbsolutePath::try_from( o.path )?; - let dir = CrateDir::try_from( path )?; + let dir = CrateDir::try_from( path.clone() )?; + + let list = action::list + ( + action::list::ListOptions::former() + .path_to_manifest( dir ) + .format( action::list::ListFormat::Tree ) + .info([ action::list::PackageAdditionalInfo::Version, action::list::PackageAdditionalInfo::Path ]) + .dependency_sources([ action::list::DependencySource::Local ]) + .dependency_categories([ action::list::DependencyCategory::Primary ]) + .form() + ) + .unwrap(); + let ListReport::Tree( mut tree ) = list else { return Err( format_err!( "Logical error. Unexpected list format" ) ) }; + let mut tasks = vec![ tree[ 0 ].clone() ]; + let mut diffs = HashMap::new(); + let mut current_idx = 0; + while current_idx < tasks.len() + { + let path = tasks[ current_idx ].path.as_ref().unwrap().to_string_lossy(); + let path = path.strip_suffix( "Cargo.toml" ).unwrap_or( &path ); + let path = AbsolutePath::try_from( path )?; + let dir = CrateDir::try_from( path.clone() )?; - let package = package::Package::try_from( dir.clone() )?; - let name = &package.name()?; - let version = &package.version()?; + let package = package::Package::try_from( dir.clone() )?; + let name = &package.name()?; + let version = &package.version()?; - _ = cargo::pack( cargo::PackOptions::former().path( dir.as_ref() ).dry( false ).form() )?; + _ = cargo::pack( cargo::PackOptions::former().path( dir.as_ref() ).allow_dirty( true ).no_verify( true ).dry( false ).form() )?; let l = CrateArchive::read( packed_crate::local_path( name, version, dir )? )?; let r = CrateArchive::download_crates_io( name, version ).unwrap(); - if let Some( out_path ) = o.keep_archive - { - _ = std::fs::create_dir_all( &out_path ); - for path in r.list() + + if let Some( out_path ) = &o.keep_archive { - let local_path = out_path.join( path ); - let folder = local_path.parent().unwrap(); - _ = std::fs::create_dir_all( folder ); + _ = std::fs::create_dir_all( &out_path ); + for path in r.list() + { + let local_path = out_path.join( path ); + let folder = local_path.parent().unwrap(); + _ = std::fs::create_dir_all( folder ); - let content = r.content_bytes( path ).unwrap(); + let content = r.content_bytes( path ).unwrap(); - std::fs::write( local_path, content )?; + std::fs::write( local_path, content )?; + } } + diffs.insert( path, crate_diff( &l, &r ).exclude( diff::PUBLISH_IGNORE_LIST ) ); + tasks.extend( tasks[ current_idx ].normal_dependencies.clone() ); + + current_idx += 1; } + let report = PublishDiffReport + { + root_path : path.clone(), + diffs, + tree : tree.remove( 0 ), + }; - Ok( crate_diff( &l, &r ).exclude( diff::PUBLISH_IGNORE_LIST ) ) + Ok( report ) } } diff --git a/module/move/willbe/src/action/readme_health_table_renew.rs b/module/move/willbe/src/action/readme_health_table_renew.rs index e6ebfe34f9..e9a9bb72e6 100644 --- a/module/move/willbe/src/action/readme_health_table_renew.rs +++ b/module/move/willbe/src/action/readme_health_table_renew.rs @@ -115,6 +115,8 @@ mod private user_and_repo: String, /// List of branches in the repository. branches: Option< Vec< String > >, + /// workspace root + workspace_root : String, } /// Structure that holds the parameters for generating a table. @@ -198,7 +200,7 @@ mod private { user_and_repo = url::git_info_extract( core_url )?; } - Ok( Self { core_url: core_url.unwrap_or_default(), user_and_repo, branches } ) + Ok( Self { core_url: core_url.unwrap_or_default(), user_and_repo, branches, workspace_root: path.to_string_lossy().to_string() } ) } } @@ -381,10 +383,73 @@ mod private } if table_parameters.include { - rou.push_str( &format!( " [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2F{}_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20{}_trivial/{}) |", &module_name, &module_name, parameters.core_url ) ); + let path = Path::new( table_parameters.base_path.as_str() ).join( &module_name ); + let p = Path::new( ¶meters.workspace_root ).join( &path ); + // let path = table_parameters.base_path. + let example = if let Some( name ) = find_example_file( p.as_path(), &module_name ) + { + let path = path.to_string_lossy().replace( '\\', "/" ).replace( "/", "%2F" ); + let tmp = name.replace( '\\', "/" ); + let file_name = tmp.split( '/' ).last().unwrap(); + let name = file_name.strip_suffix( ".rs" ).unwrap(); + format!( "[![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE={path}%2Fexamples%2F{file_name},RUN_POSTFIX=--example%20{name}/{})", parameters.core_url ) + } + else + { + "".into() + }; + rou.push_str( &format!( " {} |", example ) ); } format!( "{rou}\n" ) } + + /// todo + pub fn find_example_file(base_path : &Path, module_name : &str ) -> Option< String > + { + let examples_dir = base_path.join("examples" ); + + if examples_dir.exists() && examples_dir.is_dir() + { + if let Ok( entries ) = std::fs::read_dir( &examples_dir ) + { + for entry in entries + { + if let Ok( entry ) = entry + { + let file_name = entry.file_name(); + if let Some( file_name_str ) = file_name.to_str() + { + if file_name_str == format!( "{module_name}_trivial.rs" ) + { + return Some( entry.path().to_string_lossy().into() ) + } + } + } + } + } + } + + // If module_trivial.rs doesn't exist, return any other file in the examples directory + if let Ok( entries ) = std::fs::read_dir( &examples_dir ) + { + for entry in entries + { + if let Ok( entry ) = entry + { + let file_name = entry.file_name(); + if let Some( file_name_str ) = file_name.to_str() + { + if file_name_str.ends_with( ".rs" ) + { + return Some( entry.path().to_string_lossy().into() ) + } + } + } + } + } + + None + } /// Generate stability cell based on stability pub fn stability_generate( stability : &Stability ) -> String @@ -535,6 +600,7 @@ crate::mod_interface! protected use Stability; /// Generate Stability badge protected use stability_generate; + protected use find_example_file; /// Create Table. orphan use readme_health_table_renew; } diff --git a/module/move/willbe/src/action/readme_modules_headers_renew.rs b/module/move/willbe/src/action/readme_modules_headers_renew.rs index 4d53eec2c9..e3acf34272 100644 --- a/module/move/willbe/src/action/readme_modules_headers_renew.rs +++ b/module/move/willbe/src/action/readme_modules_headers_renew.rs @@ -12,8 +12,10 @@ mod private use std::borrow::Cow; use std::fs::{ OpenOptions }; use std::io::{ Read, Seek, SeekFrom, Write }; + use std::path::PathBuf; use convert_case::{ Case, Casing }; use regex::Regex; + use crate::action::readme_health_table_renew::find_example_file; // aaa : for Petro : rid off crate::x. ask // aaa : add `use crate::*` first @@ -27,6 +29,7 @@ mod private /// The `ModuleHeader` structure represents a set of parameters, used for creating url for header. struct ModuleHeader { + module_path : PathBuf, stability : Stability, module_name : String, repository_url : String, @@ -46,11 +49,11 @@ mod private let repository_url = package.repository()?.ok_or_else::< Error, _ >( || err!( "Fail to find repository_url in module`s Cargo.toml" ) )?; let discord_url = package.discord_url()?.or_else( || default_discord_url.clone() ); - Ok ( Self { + module_path: package.manifest_path().parent().unwrap().as_ref().to_path_buf(), stability, module_name, repository_url, @@ -60,23 +63,33 @@ mod private } /// Convert `ModuleHeader`to header. - fn to_header( self ) -> Result< String > + fn to_header( self, workspace_path : &str ) -> Result< String > { let discord = self.discord_url.map( | discord_url | - format!( "\n[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)]({discord_url})" ) + format!( " [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)]({discord_url})" ) ) .unwrap_or_default(); + let repo_url = url::extract_repo_url( &self.repository_url ).and_then( | r | url::git_info_extract( &r ).ok() ).ok_or_else::< Error, _ >( || err!( "Fail to parse repository url" ) )?; + let example = if let Some( name ) = find_example_file( self.module_path.as_path(), &self.module_name ) + { + let p = name.strip_prefix( workspace_path ).unwrap().get( 1.. ).unwrap().replace( "\\","%2F" ); + let name = name.split( "\\" ).last().unwrap().split( "." ).next().unwrap(); + format!( " [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE={p},RUN_POSTFIX=--example%20{}/https://github.com/{})", name, repo_url ) + } + else + { + "".into() + }; Ok( format! ( - "{}\ - [![rust-status](https://github.com/{}/actions/workflows/module_{}_push.yml/badge.svg)](https://github.com/{}/actions/workflows/module_{}_push.yml)\ - [![docs.rs](https://img.shields.io/docsrs/{}?color=e3e8f0&logo=docs.rs)](https://docs.rs/{})\ - [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2F{}_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20{}_trivial/https://github.com/{}){}", + "{} \ + [![rust-status](https://github.com/{}/actions/workflows/module_{}_push.yml/badge.svg)](https://github.com/{}/actions/workflows/module_{}_push.yml) \ + [![docs.rs](https://img.shields.io/docsrs/{}?color=e3e8f0&logo=docs.rs)](https://docs.rs/{}){}{}", stability_generate( &self.stability ), repo_url, self.module_name.to_case( Case::Snake ), repo_url, self.module_name.to_case( Case::Snake ), self.module_name, self.module_name, - self.module_name, self.module_name, repo_url, + example, discord, ) ) } @@ -137,7 +150,7 @@ mod private _ = query::parse( raw_params )?; - let content = header_content_generate( &content, header, raw_params )?; + let content = header_content_generate( &content, header, raw_params, cargo_metadata.workspace_root()?.to_str().unwrap() )?; file.set_len( 0 )?; file.seek( SeekFrom::Start( 0 ) )?; @@ -146,9 +159,9 @@ mod private Ok( () ) } - fn header_content_generate< 'a >( content : &'a str, header : ModuleHeader, raw_params : &str ) -> Result< Cow< 'a, str > > + fn header_content_generate< 'a >( content : &'a str, header : ModuleHeader, raw_params : &str, workspace_root : &str ) -> Result< Cow< 'a, str > > { - let header = header.to_header()?; + let header = header.to_header( workspace_root )?; let result = TAGS_TEMPLATE.get().unwrap().replace( &content, &format!( "\n{header}\n" ) ); Ok( result ) } diff --git a/module/move/willbe/src/action/test.rs b/module/move/willbe/src/action/test.rs index 3f9bd57009..72483c71d6 100644 --- a/module/move/willbe/src/action/test.rs +++ b/module/move/willbe/src/action/test.rs @@ -69,24 +69,24 @@ mod private { dir : AbsolutePath, channels : HashSet< channel::Channel >, - #[ default( 0u32 ) ] + #[ former( default = 0u32 ) ] concurrent : u32, - #[ default( 1u32 ) ] + #[ former( default = 1u32 ) ] power : u32, include_features : Vec< String >, - #[ default ( [ "full".to_string(), "default".to_string() ] ) ] + #[ former( default = [ "full".to_string(), "default".to_string() ] ) ] exclude_features : Vec< String >, - #[ default( true ) ] + #[ former( default = true ) ] temp : bool, enabled_features : Vec< String >, - #[ default( true ) ] + #[ former( default = true ) ] with_all_features : bool, - #[ default( true ) ] + #[ former( default = true ) ] with_none_features : bool, optimizations : HashSet< optimization::Optimization >, - #[ default( 1000u32 ) ] + #[ former( default = 1000u32 ) ] variants_cap : u32, - #[ default( false ) ] + #[ former( default = false ) ] with_progress : bool, } diff --git a/module/move/willbe/src/command/deploy_renew.rs b/module/move/willbe/src/command/deploy_renew.rs index 7f8137adf5..7fe0ebf13e 100644 --- a/module/move/willbe/src/command/deploy_renew.rs +++ b/module/move/willbe/src/command/deploy_renew.rs @@ -2,7 +2,7 @@ mod private { use crate::*; - use wca::Props; + use wca::VerifiedCommand; use wtools::error::{ anyhow::Context, Result }; use tool::template::Template; use action::deploy_renew::*; @@ -11,13 +11,13 @@ mod private /// Create new deploy. /// - pub fn deploy_renew( properties : Props ) -> Result< () > + pub fn deploy_renew( o : VerifiedCommand ) -> Result< () > { let current_dir = std::env::current_dir()?; let mut template = DeployTemplate::default(); _ = template.load_existing_params( ¤t_dir ); let parameters = template.parameters(); - let mut values = parameters.values_from_props( &properties ); + let mut values = parameters.values_from_props( &o.props ); for mandatory in template.get_missing_mandatory() { values.interactive_if_empty( mandatory ); diff --git a/module/move/willbe/src/command/list.rs b/module/move/willbe/src/command/list.rs index e5916cfa5c..9c970783f9 100644 --- a/module/move/willbe/src/command/list.rs +++ b/module/move/willbe/src/command/list.rs @@ -12,34 +12,35 @@ mod private collections::HashSet, }; - use wca::{ Args, Props }; + use wca::VerifiedCommand; use wtools::error::{ for_app::Context, Result }; use _path::AbsolutePath; use action::{ list as l, list::{ ListFormat, ListOptions } }; use former::Former; + // qqq: `Former` forces the struct to be public #[ derive( Former ) ] - struct ListProperties + pub struct ListProperties { - #[ default( ListFormat::Tree ) ] + #[ former( default = ListFormat::Tree ) ] format : ListFormat, - #[ default( false ) ] + #[ former( default = false ) ] with_version : bool, - #[ default( false ) ] + #[ former( default = false ) ] with_path : bool, - #[ default( true ) ] + #[ former( default = true ) ] with_local : bool, - #[ default( false ) ] + #[ former( default = false ) ] with_remote : bool, - #[ default( true ) ] + #[ former( default = true ) ] with_primary : bool, - #[ default( false ) ] + #[ former( default = false ) ] with_dev : bool, - #[ default( false ) ] + #[ former( default = false ) ] with_build : bool, } @@ -47,12 +48,12 @@ mod private /// List workspace packages. /// - pub fn list( args : Args, properties : Props ) -> Result< () > + pub fn list( o : VerifiedCommand ) -> Result< () > { - let path_to_workspace : PathBuf = args.get_owned( 0 ).unwrap_or( std::env::current_dir().context( "Workspace list command without subject" )? ); + let path_to_workspace : PathBuf = o.args.get_owned( 0 ).unwrap_or( std::env::current_dir().context( "Workspace list command without subject" )? ); let path_to_workspace = AbsolutePath::try_from( path_to_workspace )?; - let ListProperties { format, with_version, with_path, with_local, with_remote, with_primary, with_dev, with_build } = ListProperties::try_from( properties )?; + let ListProperties { format, with_version, with_path, with_local, with_remote, with_primary, with_dev, with_build } = o.props.try_into()?; let crate_dir = CrateDir::try_from( path_to_workspace )?; @@ -94,10 +95,10 @@ mod private Ok( () ) } - impl TryFrom< Props > for ListProperties + impl TryFrom< wca::Props > for ListProperties { type Error = wtools::error::for_app::Error; - fn try_from( value : Props ) -> Result< Self, Self::Error > + fn try_from( value : wca::Props ) -> Result< Self, Self::Error > { let mut this = Self::former(); diff --git a/module/move/willbe/src/command/mod.rs b/module/move/willbe/src/command/mod.rs index 0ab23d5da9..d3d5e5950b 100644 --- a/module/move/willbe/src/command/mod.rs +++ b/module/move/willbe/src/command/mod.rs @@ -103,7 +103,22 @@ pub( crate ) mod private .command( "readme.health.table.renew" ) .hint( "Generate a table for the root `Readme.md`" ) - .long_hint( "Generates a data summary table for the `Readme.md` file located in the root of the workspace." ) + .long_hint( + r#"Generates a data summary table for the `Readme.md` file located in the root of the workspace. +To ensure the proper execution of the command, the following tags need to be specified in the Readme.md file: + + + + +After executing the command, the tags will not be modified. + +Tags can contains params: + +path: The relative path to the directory from workspace root, which crates will be taken. Default is './'. +with_branches: If set to 1, it will display the status of workflow execution on branches specified in branches under workspace.metadata in the Cargo.toml of your workspace. For example, branches = ["master", "alpha"]. Default is 1. +with_stability: If set to 1, a column indicating the stability of the module will be added. Information is taken from package.metadata of each module (package.metadata.stability = "stable"). By default, the module is considered experimental. Default is 1. +with_docs: If set to 1, adds a column with a link to the module's documentation. Default is 1. +with_gitpod: If set to 1, a column with a link to Gitpod will be added. Clicking on it will open and run an example named _trivial. Default is 1."# ) .routine( command::readme_health_table_renew ) .end() diff --git a/module/move/willbe/src/command/publish.rs b/module/move/willbe/src/command/publish.rs index 2da426a841..2724b33b78 100644 --- a/module/move/willbe/src/command/publish.rs +++ b/module/move/willbe/src/command/publish.rs @@ -4,29 +4,47 @@ mod private use crate::*; use colored::Colorize; - use wca::{ Args, Props }; - use wtools::error::Result; + use wca::VerifiedCommand; + use wtools::error::{ Result, for_app::Context }; + use former::Former; + use std::fmt::Write; + // qqq: `Former` forces the struct to be public + #[ derive( Former ) ] + pub struct PublishProperties + { + #[ former( default = true ) ] + dry : bool, + #[ former( default = true ) ] + temp : bool, + } /// /// Publish package. /// - pub fn publish( args : Args, properties : Props ) -> Result< () > + pub fn publish( o : VerifiedCommand ) -> Result< () > { - let args_line = format!( "{}", args.get_owned( 0 ).unwrap_or( std::path::PathBuf::from( "" ) ).display() ); - let prop_line = format!( "{}", properties.iter().map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ).collect::< Vec< _ > >().join(" ") ); - let patterns : Vec< _ > = args.get_owned( 0 ).unwrap_or_else( || vec![ "./".into() ] ); + let args_line = format!( "{}", o.args.get_owned( 0 ).unwrap_or( std::path::PathBuf::from( "" ) ).display() ); + let prop_line = format!( "{}", o.props.iter().map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ).collect::< Vec< _ > >().join(" ") ); + + let patterns : Vec< _ > = o.args.get_owned( 0 ).unwrap_or_else( || vec![ "./".into() ] ); - let dry : bool = properties - .get_owned( "dry" ) - .unwrap_or( true ); + let PublishProperties { dry, temp } = o.props.try_into()?; + let plan = action::publish_plan( patterns, dry, temp ).context( "Failed to plan the publication process" )?; - let temp : bool = properties - .get_owned( "temp" ) - .unwrap_or( true ); + let mut formatted_plan = String::new(); + writeln!( &mut formatted_plan, "Tree :" )?; + plan.write_as_tree( &mut formatted_plan )?; + + if !plan.plans.is_empty() + { + writeln!( &mut formatted_plan, "The following packages are pending for publication :" )?; + plan.write_as_list( &mut formatted_plan )?; + } + println!( "{formatted_plan}" ); - match action::publish( patterns, dry, temp ) + match action::publish( plan ) { Ok( report ) => { @@ -50,6 +68,20 @@ mod private } } } + + impl TryFrom< wca::Props > for PublishProperties + { + type Error = wtools::error::for_app::Error; + fn try_from( value : wca::Props ) -> Result< Self, Self::Error > + { + let mut this = Self::former(); + + this = if let Some( v ) = value.get_owned( "dry" ) { this.dry::< bool >( v ) } else { this }; + this = if let Some( v ) = value.get_owned( "temp" ) { this.temp::< bool >( v ) } else { this }; + + Ok( this.form() ) + } + } } // diff --git a/module/move/willbe/src/command/publish_diff.rs b/module/move/willbe/src/command/publish_diff.rs index 85f0c29dc8..cbb029393e 100644 --- a/module/move/willbe/src/command/publish_diff.rs +++ b/module/move/willbe/src/command/publish_diff.rs @@ -3,13 +3,14 @@ mod private use crate::*; use std::path::PathBuf; - use wca::{ Args, Props }; + use wca::VerifiedCommand; use wtools::error::Result; use _path::AbsolutePath; + // qqq: `Former` forces the struct to be public #[ derive( former::Former ) ] - struct PublishDiffProperties + pub struct PublishDiffProperties { keep_archive : Option< PathBuf >, } @@ -27,10 +28,10 @@ mod private /// # Errors /// /// Returns an error if there is an issue with the command. - pub fn publish_diff( args : Args, props : Props ) -> Result< () > + pub fn publish_diff( o : VerifiedCommand ) -> Result< () > { - let path : PathBuf = args.get_owned( 0 ).unwrap_or( std::env::current_dir()? ); - let PublishDiffProperties { keep_archive } = props.try_into()?; + let path : PathBuf = o.args.get_owned( 0 ).unwrap_or( std::env::current_dir()? ); + let PublishDiffProperties { keep_archive } = o.props.try_into()?; let mut o = action::PublishDiffOptions::former() .path( path ); @@ -47,10 +48,10 @@ mod private Ok( () ) } - impl TryFrom< Props > for PublishDiffProperties + impl TryFrom< wca::Props > for PublishDiffProperties { type Error = wtools::error::for_app::Error; - fn try_from( value : Props ) -> Result< Self, Self::Error > + fn try_from( value : wca::Props ) -> Result< Self, Self::Error > { let mut this = Self::former(); diff --git a/module/move/willbe/src/command/test.rs b/module/move/willbe/src/command/test.rs index d06f749a68..e97ff16b5f 100644 --- a/module/move/willbe/src/command/test.rs +++ b/module/move/willbe/src/command/test.rs @@ -6,11 +6,7 @@ mod private use std::collections::HashSet; use std::path::PathBuf; use colored::Colorize; - use wca:: - { - Args, - Props, - }; + use wca::VerifiedCommand; use wtools::error::Result; use _path::AbsolutePath; use action::test::TestsCommandOptions; @@ -18,44 +14,46 @@ mod private use channel::Channel; use error_tools::for_app::bail; use optimization::Optimization; - + + // qqq: `Former` forces the struct to be public #[ derive( Former, Debug ) ] - struct TestsProperties + pub struct TestsProperties { - #[ default( true ) ] + #[ former( default = true ) ] dry : bool, - #[ default( true ) ] + #[ former( default = true ) ] with_stable : bool, - #[ default( false ) ] + #[ former( default = false ) ] with_nightly : bool, - #[ default( 0u32 ) ] + #[ former( default = 0u32 ) ] concurrent : u32, - #[ default( 1u32 ) ] + #[ former( default = 1u32 ) ] power : u32, include : Vec< String >, - #[ default ( [ "full".to_string(), "default".to_string() ] ) ] + #[ former( default = [ "full".to_string(), "default".to_string() ] ) ] exclude : Vec< String >, - #[ default( true ) ] + #[ former( default = true ) ] temp : bool, enabled_features : Vec< String >, - #[ default( true ) ] + #[ former( default = true ) ] with_all_features : bool, - #[ default( true ) ] + #[ former( default = true ) ] with_none_features : bool, - #[ default( true ) ] + #[ former( default = true ) ] with_debug : bool, - #[ default( false ) ] + #[ former( default = false ) ] with_release : bool, - #[ default( true ) ] + #[ former( default = true ) ] with_progress : bool, } /// run tests in specified crate - pub fn test( args : Args, properties : Props ) -> Result< () > + pub fn test( o : VerifiedCommand ) -> Result< () > { - let args_line = format!( "{}", args.get_owned( 0 ).unwrap_or( std::path::PathBuf::from( "" ) ).display() ); - let prop_line = format!( "{}", properties.iter().map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ).collect::< Vec< _ > >().join(" ") ); - let path : PathBuf = args.get_owned( 0 ).unwrap_or_else( || "./".into() ); + let args_line = format!( "{}", o.args.get_owned( 0 ).unwrap_or( std::path::PathBuf::from( "" ) ).display() ); + let prop_line = format!( "{}", o.props.iter().map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ).collect::< Vec< _ > >().join(" ") ); + + let path : PathBuf = o.args.get_owned( 0 ).unwrap_or_else( || "./".into() ); let path = AbsolutePath::try_from( path )?; let TestsProperties { @@ -73,7 +71,7 @@ mod private with_debug, with_release, with_progress - } = properties.try_into()?; + } = o.props.try_into()?; let mut channels = HashSet::new(); if with_stable { channels.insert( Channel::Stable ); } @@ -131,10 +129,10 @@ mod private } } - impl TryFrom< Props > for TestsProperties + impl TryFrom< wca::Props > for TestsProperties { type Error = wtools::error::for_app::Error; - fn try_from( value : Props ) -> Result< Self, Self::Error > + fn try_from( value : wca::Props ) -> Result< Self, Self::Error > { let mut this = Self::former(); diff --git a/module/move/willbe/src/command/workspace_renew.rs b/module/move/willbe/src/command/workspace_renew.rs index 81999f83ea..4307e20045 100644 --- a/module/move/willbe/src/command/workspace_renew.rs +++ b/module/move/willbe/src/command/workspace_renew.rs @@ -3,12 +3,13 @@ mod private use crate::*; use former::Former; - use wca::Props; + use wca::VerifiedCommand; use wtools::error::{ anyhow::Context, Result }; use action::WorkspaceTemplate; + // qqq: `Former` forces the struct to be public #[ derive( Former ) ] - struct WorkspaceNewProperties + pub struct WorkspaceNewProperties { repository_url : String, branches : Vec< String >, @@ -18,18 +19,18 @@ mod private /// Create new workspace. /// - pub fn workspace_renew( properties : Props ) -> Result< () > + pub fn workspace_renew( o : VerifiedCommand ) -> Result< () > { - let WorkspaceNewProperties { repository_url, branches } = WorkspaceNewProperties::try_from( properties )?; + let WorkspaceNewProperties { repository_url, branches } = o.props.try_into()?; let template = WorkspaceTemplate::default(); action::workspace_renew( &std::env::current_dir()?, template, repository_url, branches ).context( "Fail to create workspace" ) } - impl TryFrom< Props > for WorkspaceNewProperties + impl TryFrom< wca::Props > for WorkspaceNewProperties { type Error = wtools::error::for_app::Error; - fn try_from( value : Props ) -> std::result::Result< Self, Self::Error > + fn try_from( value : wca::Props ) -> std::result::Result< Self, Self::Error > { let mut this = Self::former(); diff --git a/module/move/willbe/src/entity/features.rs b/module/move/willbe/src/entity/features.rs index 3099f8fbd9..81c1452180 100644 --- a/module/move/willbe/src/entity/features.rs +++ b/module/move/willbe/src/entity/features.rs @@ -67,7 +67,7 @@ mod private .cloned() .collect(); - if esimate_with( filtered_features.len(), power, with_all_features, with_none_features, enabled_features, package.features().len() ) > variants_cap as usize + if estimate_with( filtered_features.len(), power, with_all_features, with_none_features, enabled_features, package.features().len() ) > variants_cap as usize { bail!( "Feature powerset longer then cap." ) } @@ -100,46 +100,54 @@ mod private Ok( features_powerset ) } - - fn esimate_with( filtered_length : usize, power : usize, with_all : bool, with_none : bool, enabled : &[ String ], unfiltred_length : usize ) -> usize + /// Calculate estimate for `features_powerset.length` + pub fn estimate_with + ( + n : usize, + power : usize, + with_all_features : bool, + with_none_features : bool, + enabled_features : &[ String ], + total_features : usize + ) + -> usize { - let mut e = esimate( filtered_length, power); - if !enabled.is_empty() && with_none - { - e += 1; - } - if with_all && power + enabled.len() >= unfiltred_length - { - e += 1; - } - e - } + let mut estimate = 0; + let mut binom = 1; + let power = power.min( n ); - fn esimate( filtered_length : usize, power : usize ) -> usize - { - let mut r = 0; - for p in 1..power + for k in 0..=power { - r += factorial( filtered_length ) / (factorial(p) * factorial( filtered_length - p ) ); + estimate += binom; + binom = binom * ( n - k ) / ( k + 1 ); } - r - } - fn factorial( n : usize ) -> usize - { - return if n == 1 - { - 1 - } - else + if with_all_features { estimate += 1; } + if with_none_features { estimate += 1; } + + if !enabled_features.is_empty() { - n * factorial(n - 1) + let len = enabled_features.len(); + let combinations = ( 0..=len.min( total_features ) ).map( | k | + { + let mut binom = 1; + for i in 0..k + { + binom = binom * ( len - i ) / ( i + 1 ); + } + binom + }).sum::< usize >(); + estimate += combinations; } + + estimate } + } crate::mod_interface! { /// Features protected use features_powerset; + protected use estimate_with; } diff --git a/module/move/willbe/src/entity/manifest.rs b/module/move/willbe/src/entity/manifest.rs index 619a5bab8c..fdb04d89bb 100644 --- a/module/move/willbe/src/entity/manifest.rs +++ b/module/move/willbe/src/entity/manifest.rs @@ -7,7 +7,7 @@ pub( crate ) mod private { io::{ self, Read }, fs, - path::Path, + path::{ Path, PathBuf }, }; use wtools::error:: { @@ -53,6 +53,21 @@ pub( crate ) mod private Ok( Self( crate_dir_path ) ) } } + + impl TryFrom< PathBuf > for CrateDir + { + type Error = CrateDirError; + + fn try_from( crate_dir_path : PathBuf ) -> Result< Self, Self::Error > + { + if !crate_dir_path.join( "Cargo.toml" ).exists() + { + return Err( CrateDirError::Validation( "The path is not a crate directory path".into() ) ); + } + + Ok( Self( AbsolutePath::try_from( crate_dir_path ).unwrap() ) ) + } + } impl CrateDir { @@ -137,6 +152,21 @@ pub( crate ) mod private impl Manifest { + /// Returns a mutable reference to the TOML document. + /// + /// If the TOML document has not been loaded yet, this function will load it + /// by calling the `load` method. If loading fails, this function will panic. + /// + /// # Returns + /// + /// A mutable reference to the TOML document. + pub fn data( &mut self ) -> &mut toml_edit::Document + { + if self.manifest_data.is_none() { self.load().unwrap() } + + self.manifest_data.as_mut().unwrap() + } + /// Returns path to `Cargo.toml`. pub fn manifest_path( &self ) -> &AbsolutePath { diff --git a/module/move/willbe/src/entity/package.rs b/module/move/willbe/src/entity/package.rs index 4fb858191e..ce0e6575fe 100644 --- a/module/move/willbe/src/entity/package.rs +++ b/module/move/willbe/src/entity/package.rs @@ -33,6 +33,8 @@ mod private use former::Former; use workspace::WorkspacePackage; use diff::crate_diff; + use version::version_revert; + use error_tools::for_app::Error; /// #[ derive( Debug, Clone ) ] @@ -302,6 +304,18 @@ mod private pub push : Option< process::Report >, } + impl std::fmt::Display for ExtendedGitReport + { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let Self { add, commit, push } = &self; + if let Some( add ) = add { writeln!( f, "{add}" )? } + if let Some( commit ) = commit { writeln!( f, "{commit}" )? } + if let Some( push ) = push { writeln!( f, "{push}" )? } + + Ok( () ) + } + } + #[ derive( Debug, Clone ) ] pub struct GitThingsOptions { @@ -311,7 +325,7 @@ mod private pub dry : bool, } - fn perform_git_operations( o : GitThingsOptions ) -> Result< ExtendedGitReport > + fn perform_git_commit( o : GitThingsOptions ) -> Result< ExtendedGitReport > { let mut report = ExtendedGitReport::default(); if o.items.is_empty() { return Ok( report ); } @@ -324,12 +338,10 @@ mod private .with_context( || format!("git_root: {}, item: {}", o.git_root.as_ref().display(), item.as_ref().display() ) ) ) .collect::< Result< Vec< _ > > >()?; - let res = git::add( &o.git_root, &items, o.dry ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + let res = git::add( &o.git_root, &items, o.dry ).map_err( | e | format_err!( "{report}\n{e}" ) )?; report.add = Some( res ); - let res = git::commit( &o.git_root, &o.message, o.dry ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + let res = git::commit( &o.git_root, &o.message, o.dry ).map_err( | e | format_err!( "{report}\n{e}" ) )?; report.commit = Some( res ); - let res = git::push( &o.git_root, o.dry ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; - report.push = Some( res ); Ok( report ) } @@ -354,7 +366,7 @@ mod private workspace_dir : CrateDir, package : Package, base_temp_dir : Option< PathBuf >, - #[ default( true ) ] + #[ former( default = true ) ] dry : bool, } @@ -367,6 +379,8 @@ mod private let pack = cargo::PackOptions { path : crate_dir.as_ref().into(), + allow_dirty : self.dry, + no_verify : self.dry, temp_path : self.base_temp_dir.clone(), dry : self.dry, }; @@ -417,7 +431,7 @@ mod private /// # Returns /// /// * `Result` - The result of the publishing operation, including information about the publish, version bump, and git operations. - pub fn perform_package_publish( instruction : PackagePublishInstruction ) -> Result< PublishReport > + pub fn perform_package_publish( instruction : PackagePublishInstruction ) -> Result< PublishReport, ( PublishReport, Error ) > { let mut report = PublishReport::default(); let PackagePublishInstruction @@ -434,15 +448,45 @@ mod private git_things.dry = dry; publish.dry = dry; - report.get_info = Some( cargo::pack( pack ).map_err( | e | format_err!( "{report}\n{e:#?}" ) )? ); + report.get_info = Some( cargo::pack( pack ).map_err( | e | ( report.clone(), e ) )? ); // qqq : redundant field? report.publish_required = true; - report.bump = Some( version::version_bump( version_bump ).map_err( | e | format_err!( "{report}\n{e:#?}" ) )? ); - let git = perform_git_operations( git_things ).map_err( |e | format_err!( "{report}\n{e:#?}" ) )?; + let bump_report = version::version_bump( version_bump ).map_err( | e | ( report.clone(), e ) )?; + report.bump = Some( bump_report.clone() ); + let git_root = git_things.git_root.clone(); + let git = match perform_git_commit( git_things ) + { + Ok( git ) => git, + Err( e ) => + { + version_revert( &bump_report ) + .map_err( | le | + ( + report.clone(), + format_err!( "Base error:\n{}\nRevert error:\n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) ) + ))?; + return Err(( report, e )); + } + }; report.add = git.add; report.commit = git.commit; - report.push = git.push; - report.publish = Some( cargo::publish( publish ).map_err( | e | format_err!( "{report}\n{e:#?}" ) )? ); + report.publish = match cargo::publish( publish ) + { + Ok( publish ) => Some( publish ), + Err( e ) => + { + git::reset( git_root.as_ref(), true, 1, false ) + .map_err( | le | + ( + report.clone(), + format_err!( "Base error:\n{}\nRevert error:\n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) ) + ))?; + return Err(( report, e )); + } + }; + + let res = git::push( &git_root, dry ).map_err( | e | ( report.clone(), e ) )?; + report.push = Some( res ); Ok( report ) } @@ -468,15 +512,18 @@ mod private /// `dry` - A boolean value indicating whether to do a dry run. If set to `true`, the application performs /// a simulated run without making any actual changes. If set to `false`, the operations are actually executed. /// This property is optional and defaults to `true`. - #[ default( true ) ] + #[ former( default = true ) ] pub dry : bool, + /// Required for tree view only + pub roots : Vec< CrateDir >, + /// `plans` - This is a vector containing the instructions for publishing each package. Each item /// in the `plans` vector indicates a `PackagePublishInstruction` set for a single package. It outlines /// how to build and where to publish the package amongst other instructions. The `#[setter( false )]` /// attribute indicates that there is no setter method for the `plans` variable and it can only be modified /// within the struct. - #[ setter( false ) ] + #[ scalar( setter = false, hint = false ) ] pub plans : Vec< PackagePublishInstruction >, } @@ -487,19 +534,20 @@ mod private /// # Arguments /// /// * `f` - A mutable reference to a `Formatter` used for writing the output. - /// * `roots` - A slice of `CrateDir` representing the root crates to display. /// /// # Errors /// /// Returns a `std::fmt::Error` if there is an error writing to the formatter. - pub fn display_as_tree( &self, f : &mut Formatter< '_ >, roots : &[ CrateDir ] ) -> std::fmt::Result + pub fn write_as_tree< W >( &self, f : &mut W ) -> std::fmt::Result + where + W : std::fmt::Write { let name_bump_report = self .plans .iter() .map( | x | ( &x.package_name, ( x.version_bump.old_version.to_string(), x.version_bump.new_version.to_string() ) ) ) .collect::< HashMap< _, _ > >(); - for wanted in roots + for wanted in &self.roots { let list = action::list ( @@ -543,7 +591,9 @@ mod private /// # Errors /// /// Returns a `std::fmt::Error` if there is an error writing to the formatter. - pub fn display_as_list( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + pub fn write_as_list< W >( &self, f : &mut W ) -> std::fmt::Result + where + W : std::fmt::Write { for ( idx, package ) in self.plans.iter().enumerate() { @@ -620,7 +670,7 @@ mod private let mut report = vec![]; for package in plan.plans { - let res = perform_package_publish( package ).map_err( | e | format_err!( "{report:#?}\n{e:#?}" ) )?; + let res = perform_package_publish( package ).map_err( |( current_rep, e )| format_err!( "{}\n{current_rep}\n{e}", report.iter().map( | r | format!( "{r}" ) ).join( "\n" ) ) )?; report.push( res ); } diff --git a/module/move/willbe/src/entity/test.rs b/module/move/willbe/src/entity/test.rs index 219fa2dfcb..7735bc5af1 100644 --- a/module/move/willbe/src/entity/test.rs +++ b/module/move/willbe/src/entity/test.rs @@ -336,11 +336,11 @@ mod private optimization : Optimization, /// Determines whether to use default features in the test. /// Enabled by default. - #[ default( true ) ] + #[ former( default = true ) ] with_default_features : bool, /// Determines whether to use all available features in the test. /// Disabled by default. - #[ default( false ) ] + #[ former( default = false ) ] with_all_features : bool, /// Specifies a list of features to be enabled in the test. enable_features : BTreeSet< String >, @@ -349,7 +349,7 @@ mod private /// A boolean indicating whether to perform a dry run or not. dry : bool, /// RUST_BACKTRACE - #[ default( true ) ] + #[ former( default = true ) ] backtrace : bool, } @@ -623,7 +623,7 @@ mod private /// actually executing them. pub dry : bool, /// Vector of succses reports. - pub succses_reports : Vec< TestReport >, + pub success_reports : Vec< TestReport >, /// Vector of failure reports. pub failure_reports : Vec< TestReport >, } @@ -638,15 +638,15 @@ mod private // qqq : for Petro : bad. should be exact command with exact parameters / при виклику зовнішніх команд повинен бути вивід у консоль про цей виклик і його аргументи за виключенням коли ційлий блок виводу прихований (у моєму випадку при фейлі) return Ok( () ) } - if self.succses_reports.is_empty() && self.failure_reports.is_empty() + if self.success_reports.is_empty() && self.failure_reports.is_empty() { writeln!( f, "The tests have not been run." )?; return Ok( () ); } - if !self.succses_reports.is_empty() + if !self.success_reports.is_empty() { writeln!( f, "Successful :" )?; - for report in &self.succses_reports + for report in &self.success_reports { writeln!( f, "{}", report )?; } @@ -660,7 +660,7 @@ mod private } } writeln!( f, "Global report" )?; - writeln!( f, " {}", generate_summary_message( self.failure_reports.len() as i32, self.succses_reports.len() as i32 ) )?; + writeln!( f, " {}", generate_summary_message( self.failure_reports.len() as i32, self.success_reports.len() as i32 ) )?; Ok( () ) } @@ -796,7 +796,7 @@ mod private { Ok( r ) => { - report.lock().unwrap().succses_reports.push( r ); + report.lock().unwrap().success_reports.push( r ); } Err(( r, _ )) => { diff --git a/module/move/willbe/src/entity/version.rs b/module/move/willbe/src/entity/version.rs index 50f851ce7d..d20329839e 100644 --- a/module/move/willbe/src/entity/version.rs +++ b/module/move/willbe/src/entity/version.rs @@ -300,6 +300,71 @@ mod private Ok( report ) } + + /// Reverts the version of a package in the provided `ExtendedBumpReport`. + /// + /// # Arguments + /// + /// * `report` - The `ExtendedBumpReport` containing the bump information. + /// + /// # Returns + /// + /// Returns `Ok(())` if the version is reverted successfully. Returns `Err` with an error message if there is any issue with reverting the version. + pub fn version_revert( report : &ExtendedBumpReport ) -> Result< () > + { + let Some( name ) = report.name.as_ref() else { return Ok( () ) }; + let Some( old_version ) = report.old_version.as_ref() else { return Ok( () ) }; + let Some( new_version ) = report.new_version.as_ref() else { return Ok( () ) }; + + let dependencies = | item_maybe_with_dependencies : &mut toml_edit::Item | + { + if let Some( dependency ) = item_maybe_with_dependencies.get_mut( "dependencies" ).and_then( | ds | ds.get_mut( name ) ) + { + if let Some( current_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( | v | v.to_string() ) + { + let version = &mut dependency[ "version" ]; + if let Some( current_version ) = current_version.strip_prefix( '~' ) + { + if current_version != new_version { return Err( format_err!( "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", version.as_str().unwrap_or_default() ) ); } + *version = value( format!( "~{}", old_version ) ); + } + else + { + if version.as_str().unwrap() != new_version { return Err( format_err!( "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", version.as_str().unwrap_or_default() ) ); } + *version = value( old_version.clone() ); + } + } + } + + Ok( () ) + }; + + for path in &report.changed_files + { + let mut manifest = manifest::open( path.clone() )?; + let data = manifest.data(); + if let Some( workspace ) = data.get_mut( "workspace" ) + { + dependencies( workspace )?; + } + if let Some( package ) = data.get_mut( "package" ) + { + if package.get_mut( "name" ).unwrap().as_str().unwrap() == name + { + let version = &mut package[ "version" ]; + if version.as_str().unwrap() != new_version { return Err( format_err!( "The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`", version.as_str().unwrap_or_default() ) ); } + *version = value( old_version.clone() ); + } + else + { + dependencies( package )?; + } + } + manifest.store()?; + } + + Ok( () ) + } } // @@ -321,4 +386,6 @@ crate::mod_interface! protected use ExtendedBumpReport; /// Bumps the version of a package and its dependencies. protected use version_bump; + /// Reverts the version of a package. + protected use version_revert; } diff --git a/module/move/willbe/src/tool/cargo.rs b/module/move/willbe/src/tool/cargo.rs index 83e376f59b..259c828791 100644 --- a/module/move/willbe/src/tool/cargo.rs +++ b/module/move/willbe/src/tool/cargo.rs @@ -14,6 +14,10 @@ mod private pub struct PackOptions { pub( crate ) path : PathBuf, + #[ former( default = false ) ] + pub( crate ) allow_dirty : bool, + #[ former( default = false ) ] + pub( crate ) no_verify : bool, pub( crate ) temp_path : Option< PathBuf >, pub( crate ) dry : bool, } @@ -33,6 +37,8 @@ mod private { [ "package".to_string() ] .into_iter() + .chain( if self.allow_dirty { Some( "--allow-dirty".to_string() ) } else { None } ) + .chain( if self.no_verify { Some( "--no-verify".to_string() ) } else { None } ) .chain( self.temp_path.clone().map( | p | vec![ "--target-dir".to_string(), p.to_string_lossy().into() ] ).into_iter().flatten() ) .collect() } diff --git a/module/move/willbe/src/tool/git.rs b/module/move/willbe/src/tool/git.rs index 844e921ac6..c90dc08109 100644 --- a/module/move/willbe/src/tool/git.rs +++ b/module/move/willbe/src/tool/git.rs @@ -138,6 +138,58 @@ mod private .run().map_err( | report | err!( report.to_string() ) ) } } + + /// This function is a wrapper around the `git reset` command. + /// + /// # Args : + /// + /// - `path`: The path to the directory on which the `git reset` command will be executed. + /// - `hard`: A boolean indicating whether to perform a hard reset or not. + /// - `commits_count`: The number of commits to reset(at least 1). + /// - `dry`: A boolean indicating whether to execute the command in dry-run mode or not. + /// + /// # Returns : + /// This function returns a `Result` containing a `Report` if the command is executed successfully. The `Report` contains the command executed, the output +// git reset command wrapper + pub fn reset< P >( path : P, hard : bool, commits_count : usize, dry : bool ) -> Result< Report > + where + P : AsRef< Path >, + { + if commits_count < 1 { return Err( err!( "Cannot reset, the count of commits must be greater than 0" ) ) } + let ( program, args ) = + ( + "git", + Some( "reset" ) + .into_iter() + .chain( if hard { Some( "--hard" ) } else { None } ) + .map( String::from ) + .chain( Some( format!( "HEAD~{}", commits_count ) ) ) + .collect::< Vec< _ > >() + ); + + if dry + { + Ok + ( + Report + { + command : format!( "{program} {}", args.join( " " ) ), + out : String::new(), + err : String::new(), + current_path : path.as_ref().to_path_buf(), + error : Ok( () ), + } + ) + } + else + { + Run::former() + .bin_path( program ) + .args( args.into_iter().map( OsString::from ).collect::< Vec< _ > >() ) + .current_path( path.as_ref().to_path_buf() ) + .run().map_err( | report | err!( report.to_string() ) ) + } + } /// Retrieves the remote URL of a Git repository. /// @@ -169,5 +221,6 @@ crate::mod_interface! protected use add; protected use commit; protected use push; + protected use reset; protected use ls_remote_url; } diff --git a/module/move/willbe/src/tool/graph.rs b/module/move/willbe/src/tool/graph.rs index 9191e31117..db91c15afb 100644 --- a/module/move/willbe/src/tool/graph.rs +++ b/module/move/willbe/src/tool/graph.rs @@ -21,6 +21,7 @@ pub( crate ) mod private use petgraph::prelude::*; use error_tools::for_lib::Error; + use error::Result; use package::{ Package, publish_need }; #[ derive( Debug, Error ) ] @@ -242,7 +243,7 @@ pub( crate ) mod private roots : &[ String ], temp_path : Option< PathBuf >, ) - -> Graph< String, String > + -> Result< Graph< String, String > > { let mut nodes = HashSet::new(); let mut cleared_graph = Graph::new(); @@ -268,8 +269,9 @@ pub( crate ) mod private .path( package.crate_dir().absolute_path().as_ref().to_path_buf() ) .option_temp_path( temp_path.clone() ) .dry( false ) + .allow_dirty( true ) .form() - ).unwrap(); + )?; if publish_need( package, temp_path.clone() ).unwrap() { nodes.insert( n ); @@ -294,7 +296,7 @@ pub( crate ) mod private } } - cleared_graph + Ok( cleared_graph ) } } diff --git a/module/move/willbe/src/tool/template.rs b/module/move/willbe/src/tool/template.rs index 9e00f793b3..044f1157b9 100644 --- a/module/move/willbe/src/tool/template.rs +++ b/module/move/willbe/src/tool/template.rs @@ -107,6 +107,7 @@ mod private #[ derive( Debug, Default, Former ) ] pub struct TemplateParameters { + #[ subform( setter = false ) ] descriptors : Vec< TemplateParameterDescriptor > } @@ -138,28 +139,16 @@ mod private is_mandatory : bool, } - impl< Context, End > TemplateParametersFormer< Context, End > + impl< Definition > TemplateParametersFormer< Definition > where - End : former::FormingEnd< TemplateParameters, Context >, + Definition : former::FormerDefinition< Storage = < TemplateParameters as former::EntityToStorage >::Storage >, { #[ inline( always ) ] pub fn parameter( self, name : &str ) -> - TemplateParameterDescriptorFormer< Self, impl former::FormingEnd< TemplateParameterDescriptor, Self > > + TemplateParameterDescriptorAsSubformer< Self, impl TemplateParameterDescriptorAsSubformerEnd< Self > > { - let on_end = | descriptor : TemplateParameterDescriptor, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if let Some( ref mut descriptors ) = super_former.storage.descriptors - { - descriptors.push( descriptor ); - } - else - { - super_former.storage.descriptors = Some( vec![ descriptor ] ); - } - super_former - }; - TemplateParameterDescriptorFormer::begin( None, Some( self ), on_end ).parameter( name ) + self._descriptors_add::< TemplateParameterDescriptorFormer< _ >, _ >() + .parameter( name ) } } @@ -313,31 +302,19 @@ mod private pub struct TemplateFilesBuilder { /// Stores all file descriptors for current template. - #[ setter( false ) ] + #[ subform( setter = true ) ] + #[ scalar( setter = false, hint = false ) ] pub files : Vec< TemplateFileDescriptor >, } - impl< Context, End > TemplateFilesBuilderFormer< Context, End > + impl< Description > TemplateFilesBuilderFormer< Description > where - End : former::FormingEnd< TemplateFilesBuilder, Context >, + Description : former::FormerDefinition< Storage = < TemplateFilesBuilder as former::EntityToStorage >::Storage >, { #[ inline( always ) ] - pub fn file( self ) -> TemplateFileDescriptorFormer< Self, impl former::FormingEnd< TemplateFileDescriptor, Self > > + pub fn file( self ) -> TemplateFileDescriptorAsSubformer< Self, impl TemplateFileDescriptorAsSubformerEnd< Self > > { - let on_end = | descriptor : TemplateFileDescriptor, super_former : core::option::Option< Self > | -> Self - { - let mut super_former = super_former.unwrap(); - if let Some( ref mut files ) = super_former.storage.files - { - files.push( descriptor ); - } - else - { - super_former.storage.files = Some( vec![ descriptor ] ); - } - super_former - }; - TemplateFileDescriptorFormer::begin( None, Some( self ), on_end ) + self._files_add() } } diff --git a/module/move/willbe/template/workflow/module_push.hbs b/module/move/willbe/template/workflow/module_push.hbs index eefdf67f9a..c3611bd498 100644 --- a/module/move/willbe/template/workflow/module_push.hbs +++ b/module/move/willbe/template/workflow/module_push.hbs @@ -2,7 +2,7 @@ name : {{name}} on : push : - branches-ignore : + branches : - 'alpha' - 'beta' - 'master' diff --git a/module/move/willbe/template/workflow/standard_rust_push.yml b/module/move/willbe/template/workflow/standard_rust_push.yml index 138307ad81..5e95425a98 100644 --- a/module/move/willbe/template/workflow/standard_rust_push.yml +++ b/module/move/willbe/template/workflow/standard_rust_push.yml @@ -39,7 +39,7 @@ jobs : runs-on: ubuntu-latest steps: - name: Install latest nightly toolchain - uses: Wandalen/wretry.action@master + uses: Wandalen/wretry.action/main@master with: action: actions-rs/toolchain@v1 with: | @@ -131,7 +131,7 @@ jobs : runs-on : ${{ matrix.os }} steps : - name : Install latest stable toolchain - uses : Wandalen/wretry.action@master + uses : Wandalen/wretry.action/main@master with : action : actions-rs/toolchain@v1 with : | @@ -140,7 +140,7 @@ jobs : attempt_limit : 3 attempt_delay: 10000 - name: Install latest nightly toolchain - uses: Wandalen/wretry.action@master + uses: Wandalen/wretry.action/main@master with: action: actions-rs/toolchain@v1 with: | diff --git a/module/move/willbe/template/workflow/standard_rust_scheduled.yml b/module/move/willbe/template/workflow/standard_rust_scheduled.yml index e39dbe1535..ac680e60bd 100644 --- a/module/move/willbe/template/workflow/standard_rust_scheduled.yml +++ b/module/move/willbe/template/workflow/standard_rust_scheduled.yml @@ -13,8 +13,6 @@ env : jobs : tested : - needs: check - if : ${{ needs.check.outputs.should_run == 'true' }} uses : Wandalen/wTools/.github/workflows/standard_rust_push.yml@alpha with : manifest_path : './Cargo.toml' diff --git a/module/move/willbe/tests/asset/full_config/_willbe_variadic_tag_configurations_c/examples/_willbe_variadic_tag_configurations_c_trivial.rs b/module/move/willbe/tests/asset/full_config/_willbe_variadic_tag_configurations_c/examples/_willbe_variadic_tag_configurations_c_trivial.rs new file mode 100644 index 0000000000..cda3d7e96f --- /dev/null +++ b/module/move/willbe/tests/asset/full_config/_willbe_variadic_tag_configurations_c/examples/_willbe_variadic_tag_configurations_c_trivial.rs @@ -0,0 +1,4 @@ +fn main() +{ + print!( "example" ); +} \ No newline at end of file diff --git a/module/move/willbe/tests/asset/single_module_with_example/Cargo.toml b/module/move/willbe/tests/asset/single_module_with_example/Cargo.toml new file mode 100644 index 0000000000..5ae4c90b46 --- /dev/null +++ b/module/move/willbe/tests/asset/single_module_with_example/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = [ + "module/test_module", +] + +[workspace.metadata] +master_branch = "test_branch" +project_name = "test" +repo_url = "https://github.com/Username/test" +discord_url = "https://discord.gg/m3YfbXpUUY" diff --git a/module/move/willbe/tests/asset/single_module_with_example/Readme.md b/module/move/willbe/tests/asset/single_module_with_example/Readme.md new file mode 100644 index 0000000000..60f5ba4c5f --- /dev/null +++ b/module/move/willbe/tests/asset/single_module_with_example/Readme.md @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/module/move/willbe/tests/asset/single_module_with_example/module/test_module/Cargo.toml b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/Cargo.toml new file mode 100644 index 0000000000..64eeb328e8 --- /dev/null +++ b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "test_module" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/Wandalen/wTools/tree/master/module/move/test_module" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html \ No newline at end of file diff --git a/module/move/willbe/tests/asset/single_module_with_example/module/test_module/Readme.md b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/Readme.md new file mode 100644 index 0000000000..8c938fa512 --- /dev/null +++ b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/Readme.md @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/module/move/willbe/tests/asset/single_module_with_example/module/test_module/examples/test_module_trivial.rs b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/examples/test_module_trivial.rs new file mode 100644 index 0000000000..17c0499e55 --- /dev/null +++ b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/examples/test_module_trivial.rs @@ -0,0 +1,4 @@ +fn main() +{ + println!( "example" ); +} \ No newline at end of file diff --git a/module/move/willbe/tests/asset/single_module_with_example/module/test_module/src/lib.rs b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/src/lib.rs new file mode 100644 index 0000000000..e9b1860dae --- /dev/null +++ b/module/move/willbe/tests/asset/single_module_with_example/module/test_module/src/lib.rs @@ -0,0 +1,17 @@ +pub fn add( left : usize, right : usize ) -> usize +{ + left + right +} + +#[ cfg( test ) ] +mod tests +{ + use super::*; + + #[ test ] + fn it_works() + { + let result = add( 2, 2 ); + assert_eq!( result, 4 ); + } +} diff --git a/module/move/willbe/tests/inc/action/cicd_renew.rs b/module/move/willbe/tests/inc/action/cicd_renew.rs index ddd92bb908..a2da8fec84 100644 --- a/module/move/willbe/tests/inc/action/cicd_renew.rs +++ b/module/move/willbe/tests/inc/action/cicd_renew.rs @@ -78,7 +78,7 @@ fn default_case() let mut push_map = HashMap::new(); push_map.insert ( - "branches-ignore".to_string(), + "branches".to_string(), vec![ "alpha".to_string(), "beta".to_string(), "master".to_string() ], ); map.insert( "push".to_string(), push_map ); diff --git a/module/move/willbe/tests/inc/action/list/data.rs b/module/move/willbe/tests/inc/action/list/data.rs index 43ade141cb..17fc6230b6 100644 --- a/module/move/willbe/tests/inc/action/list/data.rs +++ b/module/move/willbe/tests/inc/action/list/data.rs @@ -275,7 +275,9 @@ mod workspace_with_cyclic_dependency // (*) - means duplication let ultra_sub_tree = &mega_sub_tree.normal_dependencies[ 0 ]; - assert_eq!( "_workspace_with_cyclic_dep_b (*)", ultra_sub_tree.name.as_str() ); + assert_eq!( "_workspace_with_cyclic_dep_b", ultra_sub_tree.name.as_str() ); + assert_eq!( "*", sub_tree.version.as_ref().unwrap().as_str() ); + assert!( ultra_sub_tree.duplicate ); assert_eq!( "*", ultra_sub_tree.version.as_ref().unwrap().as_str() ); assert!( ultra_sub_tree.normal_dependencies.is_empty() ); diff --git a/module/move/willbe/tests/inc/action/list/format.rs b/module/move/willbe/tests/inc/action/list/format.rs index 23d97d6414..42d492cf9d 100644 --- a/module/move/willbe/tests/inc/action/list/format.rs +++ b/module/move/willbe/tests/inc/action/list/format.rs @@ -10,6 +10,7 @@ fn node_with_depth_two_leaves_stop_spacer() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec! [ ListNodeReport @@ -17,11 +18,13 @@ fn node_with_depth_two_leaves_stop_spacer() name : "sub_node1".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![ ListNodeReport { name : "sub_sub_node1".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -34,11 +37,13 @@ fn node_with_depth_two_leaves_stop_spacer() name : "sub_node2".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![ ListNodeReport { name : "sub_sub_node2".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -73,6 +78,7 @@ fn node_with_depth_two_leaves() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec! [ ListNodeReport @@ -80,11 +86,13 @@ fn node_with_depth_two_leaves() name : "sub_node1".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![ ListNodeReport { name : "sub_sub_node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -97,6 +105,7 @@ fn node_with_depth_two_leaves() name : "sub_node2".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -127,16 +136,19 @@ fn node_with_depth_one_leaf() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![ ListNodeReport { name : "sub_node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![ ListNodeReport { name : "sub_sub_node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -168,6 +180,7 @@ fn node_with_build_dependencies_tree_with_two_leaves() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec! @@ -177,6 +190,7 @@ fn node_with_build_dependencies_tree_with_two_leaves() name : "build_sub_node1".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -186,6 +200,7 @@ fn node_with_build_dependencies_tree_with_two_leaves() name : "build_sub_node2".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -214,6 +229,7 @@ fn node_with_build_dependencies_tree_with_one_leaf() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![ @@ -222,6 +238,7 @@ fn node_with_build_dependencies_tree_with_one_leaf() name : "build_sub_node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -249,6 +266,7 @@ fn node_with_dev_dependencies_tree_with_two_leaves() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec! [ @@ -257,6 +275,7 @@ fn node_with_dev_dependencies_tree_with_two_leaves() name : "dev_sub_node1".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -266,6 +285,7 @@ fn node_with_dev_dependencies_tree_with_two_leaves() name : "dev_sub_node2".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -295,6 +315,7 @@ fn node_with_dev_dependencies_tree_with_one_leaf() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![ ListNodeReport @@ -302,6 +323,7 @@ fn node_with_dev_dependencies_tree_with_one_leaf() name : "dev_sub_node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -330,6 +352,7 @@ fn node_with_dependencies_tree_with_two_leaves() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec! [ ListNodeReport @@ -337,6 +360,7 @@ fn node_with_dependencies_tree_with_two_leaves() name : "sub_node1".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -346,6 +370,7 @@ fn node_with_dependencies_tree_with_two_leaves() name : "sub_node2".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -375,11 +400,13 @@ fn node_with_dependency_tree_with_one_leaf() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![ ListNodeReport { name : "sub_node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], @@ -407,6 +434,7 @@ fn one_node_one_line() name : "node".into(), version : None, path : None, + duplicate : false, normal_dependencies : vec![], dev_dependencies : vec![], build_dependencies : vec![], diff --git a/module/move/willbe/tests/inc/action/readme_health_table_renew.rs b/module/move/willbe/tests/inc/action/readme_health_table_renew.rs index f085fe694e..58a745148e 100644 --- a/module/move/willbe/tests/inc/action/readme_health_table_renew.rs +++ b/module/move/willbe/tests/inc/action/readme_health_table_renew.rs @@ -78,7 +78,6 @@ fn stability_and_repository_from_module_toml() _ = file.read_to_string( &mut actual ).unwrap(); assert!( actual.contains( "[![stability-stable](https://img.shields.io/badge/stability-stable-green.svg)](https://github.com/emersion/stability-badges#stable)" ) ); - assert!( actual.contains( "https://github.com/Testusername/TestProject" ) ); } #[ test ] @@ -198,5 +197,5 @@ fn sample_cell() let mut actual = String::new(); _ = file.read_to_string( &mut actual ).unwrap(); - assert!( actual.contains( "[![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2F_willbe_variadic_tag_configurations_c_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20_willbe_variadic_tag_configurations_c_trivial/https://github.com/SomeName/SomeCrate/C)" ) ); + assert!( actual.contains( " [![Open in Gitpod](https://raster.shields.io/static/v1?label=&message=try&color=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=.%2F_willbe_variadic_tag_configurations_c%2Fexamples%2F_willbe_variadic_tag_configurations_c_trivial.rs,RUN_POSTFIX=--example%20_willbe_variadic_tag_configurations_c_trivial/https://github.com/SomeName/SomeCrate/C)" ) ); } diff --git a/module/move/willbe/tests/inc/action/readme_modules_headers_renew.rs b/module/move/willbe/tests/inc/action/readme_modules_headers_renew.rs index 3b731914bb..8fe71df94a 100644 --- a/module/move/willbe/tests/inc/action/readme_modules_headers_renew.rs +++ b/module/move/willbe/tests/inc/action/readme_modules_headers_renew.rs @@ -78,21 +78,38 @@ fn docs() } #[ test ] -fn gitpod() +fn no_gitpod() { // Arrange - let temp = arrange( "single_module" ); + let temp = arrange("single_module"); + + // Act + _ = action::readme_modules_headers_renew(AbsolutePath::try_from(temp.path()).unwrap()).unwrap(); + let mut file = std::fs::File::open(temp.path().join("test_module").join("Readme.md")).unwrap(); + + let mut actual = String::new(); + + _ = file.read_to_string(&mut actual).unwrap(); + + // Assert + // no example - no gitpod + assert!(!actual.contains("[Open in Gitpod]")); +} +#[ test ] +fn with_gitpod() +{ + let temp = arrange( "single_module_with_example" ); // Act _ = action::readme_modules_headers_renew( AbsolutePath::try_from( temp.path() ).unwrap() ).unwrap(); - let mut file = std::fs::File::open( temp.path().join( "test_module" ).join( "Readme.md" ) ).unwrap(); + let mut file = std::fs::File::open( temp.path().join( "module" ).join( "test_module" ).join( "Readme.md" ) ).unwrap(); let mut actual = String::new(); _ = file.read_to_string( &mut actual ).unwrap(); - // Assert - assert!( actual.contains( "[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftest_module_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20test_module_trivial/https://github.com/Wandalen/wTools)" ) ); + dbg!(&actual); + assert!( actual.contains( "[Open in Gitpod]" ) ); } #[ test ] diff --git a/module/move/willbe/tests/inc/action/test.rs b/module/move/willbe/tests/inc/action/test.rs index 530a894704..1233017b05 100644 --- a/module/move/willbe/tests/inc/action/test.rs +++ b/module/move/willbe/tests/inc/action/test.rs @@ -145,7 +145,7 @@ fn call_from_workspace_root() assert_eq!( rep.failure_reports.len(), 1 ); - assert_eq!( rep.succses_reports.len(), 2 ); + assert_eq!( rep.success_reports.len(), 2 ); } #[ test ] @@ -174,7 +174,7 @@ fn plan() .with_progress( false ) .form(); - let rep = test( args, true ).unwrap().succses_reports[ 0 ].clone().tests; + let rep = test( args, true ).unwrap().success_reports[ 0 ].clone().tests; assert!( rep.get( &TestVariant::former().optimization( Optimization::Debug ).channel( Channel::Stable ).features( BTreeSet::default() ).form() ).is_some() ); assert!( rep.get( &TestVariant::former().optimization( Optimization::Debug ).channel( Channel::Nightly ).features( BTreeSet::default() ).form() ).is_some() ); diff --git a/module/move/willbe/tests/inc/entity/diff.rs b/module/move/willbe/tests/inc/entity/diff.rs new file mode 100644 index 0000000000..b3362ee64d --- /dev/null +++ b/module/move/willbe/tests/inc/entity/diff.rs @@ -0,0 +1,98 @@ +use crate::*; + +use std::path::{ Path, PathBuf }; +use assert_fs::{ TempDir, prelude::* }; +use crates_tools::CrateArchive; +use the_module::*; +use _path::AbsolutePath; +use package::Package; +use diff::crate_diff; +use the_module::version::{ Version, BumpOptions, version_bump }; + +const TEST_MODULE_PATH : &str = "../../test/"; + +#[ test ] +fn no_changes() +{ + let tmp = &TempDir::new().unwrap(); + let package_path = package_path( "c" ); + + let left = prepare( tmp, "left", &package_path ); + let left_crate = crate_file_path( &left ); + let left_archive = CrateArchive::read( &left_crate ).unwrap(); + + let right = prepare( tmp, "right", &package_path ); + let right_crate = crate_file_path( &right ); + let right_archive = CrateArchive::read( &right_crate ).unwrap(); + + let has_changes = crate_diff( &left_archive, &right_archive ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes(); + + assert!( !has_changes ); +} + +#[ test ] +fn with_changes() +{ + let tmp = &TempDir::new().unwrap(); + let package_path = package_path( "c" ); + + let left = + { + let left = prepare( tmp, "left", &package_path ); + let left_crate = crate_file_path( &left ); + CrateArchive::read( &left_crate ).unwrap() + }; + + let right = + { + let right = prepare( tmp, "right", &package_path ); + + let absolute = AbsolutePath::try_from( right.as_path() ).unwrap(); + let right_package = Package::try_from( absolute ).unwrap(); + let right_version = Version::try_from( &right_package.version().unwrap() ).unwrap(); + + let bump_options = BumpOptions + { + crate_dir : CrateDir::try_from( right.clone() ).unwrap(), + old_version : right_version.clone(), + new_version : right_version.bump(), + dependencies : vec![], + dry : false, + }; + version_bump( bump_options ).unwrap(); + + let right_crate = crate_file_path( &right ); + CrateArchive::read( &right_crate ).unwrap() + }; + + let has_changes = crate_diff( &left, &right ).exclude( diff::PUBLISH_IGNORE_LIST ).has_changes(); + + assert!( has_changes ); +} + +fn package_path< P : AsRef< Path > >( path : P ) -> PathBuf +{ + let root_path = Path::new( env!( "CARGO_MANIFEST_DIR" ) ).join( TEST_MODULE_PATH ); + root_path.join( path ) +} + +fn prepare( tmp : &TempDir, name : &str, manifest_dir_path : &Path ) -> PathBuf +{ + let dir = tmp.child( name ); + dir.create_dir_all().unwrap(); + dir.copy_from( manifest_dir_path, &[ "**" ] ).unwrap(); + + dir.to_path_buf() +} + +fn crate_file_path( manifest_dir_path : &Path ) -> PathBuf +{ + _ = cargo::pack( cargo::PackOptions::former().path( manifest_dir_path ).dry( false ).form() ).expect( "Failed to package a package" ); + + let absolute = AbsolutePath::try_from( manifest_dir_path ).unwrap(); + let package = Package::try_from( absolute ).unwrap(); + manifest_dir_path + .join( "target" ) + .join( "package" ) + .join( format!( "{}-{}.crate", package.name().unwrap(), package.version().unwrap() ) ) +} diff --git a/module/move/willbe/tests/inc/entity/features.rs b/module/move/willbe/tests/inc/entity/features.rs index 3ee575389a..dd4db5bff8 100644 --- a/module/move/willbe/tests/inc/entity/features.rs +++ b/module/move/willbe/tests/inc/entity/features.rs @@ -6,6 +6,7 @@ use the_module::features::features_powerset; use std::collections::HashMap; use serde::Deserialize; use the_module::workspace::WorkspacePackage; +use willbe::features::estimate_with; /// Constructs a mock `Package` with specified features for testing. fn mock_package( features : Vec< ( &str, Vec< &str > ) > ) -> WorkspacePackage @@ -254,4 +255,14 @@ fn case_6() assert!( result.contains( &vec![ "f2".to_string() ].into_iter().collect()) ); assert_eq!( result.len(), 2 ); +} + +#[ test ] +fn estimate() +{ + assert_eq!( estimate_with( 5, 2, false, false, &[], 0 ), 16 ); + assert_eq!( estimate_with( 5, 2, true, false, &[], 0 ), 17 ); + assert_eq!( estimate_with( 5, 2, false, true, &[], 0 ), 17 ); + assert_eq!( estimate_with( 5, 2, false, false, &[ "feature1".to_string(), "feature2".to_string() ], 2 ), 20 ); + assert_eq!( estimate_with( 5, 2, true, true, &[ "feature1".to_string(), "feature2".to_string() ], 2 ), 22 ); } \ No newline at end of file diff --git a/module/move/willbe/tests/inc/entity/mod.rs b/module/move/willbe/tests/inc/entity/mod.rs index d86862bb87..58ee035a97 100644 --- a/module/move/willbe/tests/inc/entity/mod.rs +++ b/module/move/willbe/tests/inc/entity/mod.rs @@ -1,9 +1,6 @@ use super::*; +pub mod dependencies; +pub mod diff; pub mod features; - pub mod version; - -pub mod publish_need; - -pub mod dependencies; \ No newline at end of file diff --git a/module/move/willbe/tests/inc/entity/publish_need.rs b/module/move/willbe/tests/inc/entity/publish_need.rs deleted file mode 100644 index 59f4a97828..0000000000 --- a/module/move/willbe/tests/inc/entity/publish_need.rs +++ /dev/null @@ -1,134 +0,0 @@ -use super::*; - -use std:: -{ - io::Write, - path::{ Path, PathBuf }, -}; - -use assert_fs::prelude::*; -use the_module:: -{ - package::{ publish_need, Package }, - _path::AbsolutePath, - manifest, - version, - cargo -}; - -const TEST_MODULE_PATH : &str = "../../test/"; - -fn package_path< P : AsRef< Path > >( path : P ) -> PathBuf -{ - let root_path = Path::new( env!( "CARGO_MANIFEST_DIR" ) ).join( TEST_MODULE_PATH ); - root_path.join( path ) -} - -fn package< P : AsRef< Path > >( path : P ) -> Package -{ - let path = path.as_ref(); - _ = cargo::pack( cargo::PackOptions::former().path( path.to_path_buf() ).dry( false ).form() ).expect( "Failed to package a package" ); - let absolute = AbsolutePath::try_from( path ).unwrap(); - - Package::try_from( absolute ).unwrap() -} - -// published the same as local -#[ test ] -fn no_changes() -{ - // Arrange - // aaa : for Bohdan : make helper function returning package_path. reuse it for all relevant tests - // aaa : use `package_path` function - let package_path = package_path( "c" ); - - _ = cargo::pack( cargo::PackOptions::former().path( package_path.clone() ).dry( false ).form() ).expect( "Failed to package a package" ); - let absolute = AbsolutePath::try_from( package_path ).unwrap(); - let package = Package::try_from( absolute ).unwrap(); - - // Act - let publish_needed = publish_need( &package, None ).unwrap(); - - // Assert - assert!( !publish_needed ); -} - -// version bumped => publish required -#[ test ] -fn with_changes() -{ - // Arrange - let package_path = package_path( "c" ); - - let temp = assert_fs::TempDir::new().unwrap(); - temp.copy_from( &package_path, &[ "**" ] ).unwrap(); - - let absolute = AbsolutePath::try_from( temp.as_ref() ).unwrap(); - let mut manifest = manifest::open( absolute ).unwrap(); - version::bump( &mut manifest, false ).unwrap(); - - _ = cargo::pack( cargo::PackOptions::former().path( temp.path().to_path_buf() ).dry( false ).form() ).expect( "Failed to package a package" ); - - let absolute = AbsolutePath::try_from( temp.as_ref() ).unwrap(); - let package = Package::try_from( absolute ).unwrap(); - - // Act - let publish_needed = publish_need( &package, None ).unwrap(); - - // Assert - assert!( publish_needed ); -} - -// c(update) -> b(re-publish) -> a(re-publish) -#[ test ] -fn cascade_with_changes() -{ - let abc = [ "a", "b", "c" ].into_iter().map( package_path ).map( package ).collect::< Vec< _ > >(); - let [ a, b, c ] = abc.as_slice() else { unreachable!() }; - if ![ c, b, a ].into_iter().inspect( | x | { dbg!( x.name().unwrap() ); } ).map( | a | publish_need( a, None ) ).inspect( | x | { dbg!(x); } ).all( | p | !p.expect( "There was an error verifying whether the package needs publishing or not" ) ) - { - panic!( "The packages must be up-to-dated" ); - } - let temp = assert_fs::TempDir::new().unwrap(); - let temp_module = temp.child( "module" ); - std::fs::create_dir( &temp_module ).unwrap(); - temp_module.child( "a" ).copy_from( a.manifest_path().parent().unwrap(), &[ "**" ] ).unwrap(); - temp_module.child( "b" ).copy_from( b.manifest_path().parent().unwrap(), &[ "**" ] ).unwrap(); - temp_module.child( "c" ).copy_from( c.manifest_path().parent().unwrap(), &[ "**" ] ).unwrap(); - let a_temp_path = temp_module.join( "a" ); - let b_temp_path = temp_module.join( "b" ); - let c_temp_path = temp_module.join( "c" ); - - let mut cargo_toml = std::fs::File::create( temp.join( "Cargo.toml" ) ).unwrap(); - write!( cargo_toml, r#" -[workspace] -resolver = "2" -members = [ - "module/*", -] -[workspace.dependencies.test_experimental_a] -version = "*" -path = "module/a" -default-features = true -[workspace.dependencies.test_experimental_b] -version = "*" -path = "module/b" -default-features = true -[workspace.dependencies.test_experimental_c] -version = "*" -path = "module/c" -default-features = true -"# ).unwrap(); - - let absolute = AbsolutePath::try_from( c_temp_path.join( "Cargo.toml" ) ).unwrap(); - let mut manifest = manifest::open( absolute ).unwrap(); - version::bump( &mut manifest, false ).unwrap(); - - let c_temp = package( c_temp_path ); - let b_temp = package( b_temp_path ); - let a_temp = package( a_temp_path ); - - assert!( publish_need( &c_temp, None ).unwrap() ); - assert!( publish_need( &b_temp, None ).unwrap() ); - assert!( publish_need( &a_temp, None ).unwrap() ); -} diff --git a/module/move/willbe/tests/inc/entity/version.rs b/module/move/willbe/tests/inc/entity/version.rs index 32362ba6fa..328bd07834 100644 --- a/module/move/willbe/tests/inc/entity/version.rs +++ b/module/move/willbe/tests/inc/entity/version.rs @@ -1,5 +1,26 @@ -use crate::the_module::version::Version; +use crate::*; + +use std::path::{ Path, PathBuf }; use std::str::FromStr; +use std::io::Write; +use assert_fs::prelude::*; +use the_module:: +{ + CrateDir, + Manifest, + version::Version, + _path::AbsolutePath, + package::Package, + version::{ BumpOptions, version_bump, version_revert }, +}; + +const TEST_MODULE_PATH : &str = "../../test/"; + +fn package_path< P : AsRef< Path > >( path : P ) -> PathBuf +{ + let root_path = Path::new( env!( "CARGO_MANIFEST_DIR" ) ).join( TEST_MODULE_PATH ); + root_path.join( path ) +} #[ test ] fn patch() @@ -78,3 +99,129 @@ fn major_with_patches() // Assert assert_eq!( "2.0.0", &new_version.to_string() ); } + +#[ test ] +fn package_version_bump() +{ + // Arrange + let c = package_path( "c" ); + let temp = assert_fs::TempDir::new().unwrap(); + let temp_module = temp.child( "module" ); + std::fs::create_dir( &temp_module ).unwrap(); + temp_module.child( "c" ).copy_from( &c, &[ "**" ] ).unwrap(); + let c_temp_path = temp_module.join( "c" ); + let c_temp_absolute_path = AbsolutePath::try_from( c_temp_path ).unwrap(); + let c_temp_crate_dir = CrateDir::try_from( c_temp_absolute_path.clone() ).unwrap(); + let c_package = Package::try_from( c_temp_absolute_path.clone() ).unwrap(); + let version = c_package.version().unwrap(); + + let root_manifest_path = temp.join( "Cargo.toml" ); + let mut cargo_toml = std::fs::File::create( &root_manifest_path ).unwrap(); + let root_manifest_absolute_path = AbsolutePath::try_from( root_manifest_path.as_path() ).unwrap(); + write!( cargo_toml, r#" +[workspace] +resolver = "2" +members = [ + "module/*", +] +[workspace.dependencies.test_experimental_c] +version = "{version}" +path = "module/c" +default-features = true +"# ).unwrap(); + let version = Version::try_from( &version ).unwrap(); + let bumped_version = version.clone().bump(); + + // Act + let options = BumpOptions + { + crate_dir : c_temp_crate_dir, + old_version : version.clone(), + new_version : bumped_version.clone(), + dependencies : vec![ CrateDir::try_from( root_manifest_absolute_path.parent().unwrap() ).unwrap() ], + dry : false, + }; + let bump_report = version_bump( options ).unwrap(); + + // Assert + assert_eq!( Some( version.to_string() ), bump_report.old_version ); + assert_eq!( Some( bumped_version.to_string() ), bump_report.new_version ); + assert_eq! + ( + { + let mut v = vec![ root_manifest_absolute_path.clone(), c_temp_absolute_path.join( "Cargo.toml" ) ]; + v.sort(); + v + }, + { + let mut v = bump_report.changed_files; + v.sort(); + v + } + ); + let c_package = Package::try_from( c_temp_absolute_path.clone() ).unwrap(); + let name = c_package.name().unwrap(); + assert_eq!( bumped_version.to_string(), c_package.version().unwrap() ); + let mut root_manifest = Manifest::try_from( root_manifest_absolute_path ).unwrap(); + root_manifest.load().unwrap(); + let data = root_manifest.data(); + let current_version_item = data.get( "workspace" ).and_then( | w | w.get( "dependencies" ) ).and_then( | d | d.get( &name ) ).and_then( | p | p.get( "version" ) ).unwrap(); + let current_version = current_version_item.as_str().unwrap(); + assert_eq!( &bumped_version.to_string(), current_version ); +} + +#[ test ] +fn package_version_bump_revert() +{ + // Arrange + let c = package_path( "c" ); + let temp = assert_fs::TempDir::new().unwrap(); + let temp_module = temp.child( "module" ); + std::fs::create_dir( &temp_module ).unwrap(); + temp_module.child( "c" ).copy_from( &c, &[ "**" ] ).unwrap(); + let c_temp_path = temp_module.join( "c" ); + let c_temp_absolute_path = AbsolutePath::try_from( c_temp_path ).unwrap(); + let c_temp_crate_dir = CrateDir::try_from( c_temp_absolute_path.clone() ).unwrap(); + let c_package = Package::try_from( c_temp_absolute_path.clone() ).unwrap(); + let version = c_package.version().unwrap(); + + let root_manifest_path = temp.join( "Cargo.toml" ); + let mut cargo_toml = std::fs::File::create( &root_manifest_path ).unwrap(); + let root_manifest_absolute_path = AbsolutePath::try_from( root_manifest_path.as_path() ).unwrap(); + write!( cargo_toml, r#" +[workspace] +resolver = "2" +members = [ + "module/*", +] +[workspace.dependencies.test_experimental_c] +version = "{version}" +path = "module/c" +default-features = true +"# ).unwrap(); + let version = Version::try_from( &version ).unwrap(); + let bumped_version = version.clone().bump(); + + // Act + let options = BumpOptions + { + crate_dir : c_temp_crate_dir, + old_version : version.clone(), + new_version : bumped_version.clone(), + dependencies : vec![ CrateDir::try_from( root_manifest_absolute_path.parent().unwrap() ).unwrap() ], + dry : false, + }; + let bump_report = version_bump( options ).unwrap(); + version_revert( &bump_report ).unwrap(); + + // Assert + let c_package = Package::try_from( c_temp_absolute_path.clone() ).unwrap(); + let name = c_package.name().unwrap(); + assert_eq!( version.to_string(), c_package.version().unwrap() ); + let mut root_manifest = Manifest::try_from( root_manifest_absolute_path ).unwrap(); + root_manifest.load().unwrap(); + let data = root_manifest.data(); + let current_version_item = data.get( "workspace" ).and_then( | w | w.get( "dependencies" ) ).and_then( | d | d.get( &name ) ).and_then( | p | p.get( "version" ) ).unwrap(); + let current_version = current_version_item.as_str().unwrap(); + assert_eq!( &version.to_string(), current_version ); +} diff --git a/module/move/willbe/tests/inc/package.rs b/module/move/willbe/tests/inc/package.rs index 08b2f36f93..935069b5e6 100644 --- a/module/move/willbe/tests/inc/package.rs +++ b/module/move/willbe/tests/inc/package.rs @@ -1,12 +1,12 @@ -use super::*; -use the_module:: -{ - Workspace, - _path::AbsolutePath, - package::PublishPlan, -}; -use willbe::package::perform_packages_publish; - +// use super::*; +// use the_module:: +// { +// Workspace, +// _path::AbsolutePath, +// package::PublishPlan, +// }; +// use willbe::package::perform_packages_publish; +// // #[ test ] // fn plan_publish_many_packages() // { diff --git a/module/move/wplot/Readme.md b/module/move/wplot/Readme.md index 736c33de46..7819424b13 100644 --- a/module/move/wplot/Readme.md +++ b/module/move/wplot/Readme.md @@ -2,8 +2,7 @@ # Module :: wplot - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml)[![docs.rs](https://img.shields.io/docsrs/wplot?color=e3e8f0&logo=docs.rs)](https://docs.rs/wplot)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Fwplot_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20wplot_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_wplot_push.yml) [![docs.rs](https://img.shields.io/docsrs/wplot?color=e3e8f0&logo=docs.rs)](https://docs.rs/wplot) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) Plot interface. diff --git a/module/template/template_blank/Readme.md b/module/template/template_blank/Readme.md index c1c128cd89..1651492515 100644 --- a/module/template/template_blank/Readme.md +++ b/module/template/template_blank/Readme.md @@ -1,7 +1,7 @@ # Module :: {{template_blank}} -[![experimental](https://raster.shields.io/static/v1?label=stability&message=experimental&color=orange&logoColor=eee)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/Module{{TemplateBlank}}Push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Module{{TemplateBlank}}Push.yml) [![docs.rs](https://img.shields.io/docsrs/{{template_blank}}?color=e3e8f0&logo=docs.rs)](https://docs.rs/{{template_blank}}) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) +[![experimental](https://raster.shields.io/static/v1?label=stability&message=experimental&color=orange&logoColor=eee)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/Module{{template_blank}}Push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/Module{{template_blank}}Push.yml) [![docs.rs](https://img.shields.io/docsrs/{{template_blank}}?color=e3e8f0&logo=docs.rs)](https://docs.rs/{{template_blank}}) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) ___ diff --git a/module/test/a/Readme.md b/module/test/a/Readme.md index 4e217e53a9..15883cfdfa 100644 --- a/module/test/a/Readme.md +++ b/module/test/a/Readme.md @@ -1,4 +1,3 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_a_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_a_push.yml)[![docs.rs](https://img.shields.io/docsrs/test_experimental_a?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_experimental_a)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftest_experimental_a_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20test_experimental_a_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_a_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_a_push.yml) [![docs.rs](https://img.shields.io/docsrs/test_experimental_a?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_experimental_a) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) diff --git a/module/test/b/Readme.md b/module/test/b/Readme.md index d06086c433..2be10ffb69 100644 --- a/module/test/b/Readme.md +++ b/module/test/b/Readme.md @@ -1,4 +1,3 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_b_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_b_push.yml)[![docs.rs](https://img.shields.io/docsrs/test_experimental_b?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_experimental_b)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftest_experimental_b_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20test_experimental_b_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_b_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_b_push.yml) [![docs.rs](https://img.shields.io/docsrs/test_experimental_b?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_experimental_b) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) diff --git a/module/test/c/Readme.md b/module/test/c/Readme.md index 547b2cd045..89ea49aafc 100644 --- a/module/test/c/Readme.md +++ b/module/test/c/Readme.md @@ -1,4 +1,3 @@ - [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental)[![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_c_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_c_push.yml)[![docs.rs](https://img.shields.io/docsrs/test_experimental_c?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_experimental_c)[![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE=sample%2Frust%2Ftest_experimental_c_trivial%2Fsrc%2Fmain.rs,RUN_POSTFIX=--example%20test_experimental_c_trivial/https://github.com/Wandalen/wTools) -[![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY) + [![experimental](https://raster.shields.io/static/v1?label=&message=experimental&color=orange)](https://github.com/emersion/stability-badges#experimental) [![rust-status](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_c_push.yml/badge.svg)](https://github.com/Wandalen/wTools/actions/workflows/module_test_experimental_c_push.yml) [![docs.rs](https://img.shields.io/docsrs/test_experimental_c?color=e3e8f0&logo=docs.rs)](https://docs.rs/test_experimental_c) [![discord](https://img.shields.io/discord/872391416519737405?color=eee&logo=discord&logoColor=eee&label=ask)](https://discord.gg/m3YfbXpUUY)